• Size of unsigned short type. Data types in C. Programming in C language

    Language Basics

    The program code and the data that the program manipulates are recorded in the computer's memory as a sequence of bits. Bit- is the smallest element computer memory, capable of storing either 0 or 1. On physical level this matches electrical voltage, which, as is known, either exists or does not. Looking at the contents of the computer's memory, we will see something like:
    ...

    It is very difficult to make sense of such a sequence, but sometimes we need to manipulate such unstructured data (usually the need for this arises when programming hardware device drivers). C++ provides a set of operations for working with bit data. (We'll talk about this in Chapter 4.)
    Typically, some structure is imposed on a sequence of bits, grouping the bits into bytes And words. A byte contains 8 bits, and a word contains 4 bytes, or 32 bits. However, the definition of the word may be different on different operating systems. The transition to 64-bit systems is now beginning, and more recently systems with 16-bit words have been widespread. Although the vast majority of systems have the same byte size, we will still call these quantities machine-specific.

    Now we can talk about, for example, a byte at address 1040 or a word at address 1024 and say that a byte at address 1032 is not equal to a byte at address 1040.
    However, we do not know what any byte, any machine word, represents. How to understand the meaning of certain 8 bits? In order to uniquely interpret the meaning of this byte (or word, or other set of bits), we must know the type of data that the byte represents.
    C++ provides a set of built-in data types: character, integer, real - and a set of composite and extended types: strings, arrays, complex numbers. In addition, for actions with this data there is basic set operations: comparison, arithmetic and other operations. There are also transition operators, loops, and conditional operators. These elements of the C++ language make up the set of bricks from which you can build a system of any complexity. The first step in mastering C++ will be to study the listed basic elements, which is what Part II of this book is devoted to.
    Chapter 3 provides an overview of built-in and extended types, and the mechanisms by which you can create new types. This is mainly, of course, the class mechanism introduced in Section 2.3. Chapter 4 covers expressions, built-in operations and their priorities, and type conversions. Chapter 5 talks about language instructions. Finally, Chapter 6 introduces the C++ Standard Library and the container types vector and associative array.

    3. C++ data types

    This chapter provides an overview built-in, or elementary, data types of the C++ language. It starts with a definition literals, such as 3.14159 or pi, and then the concept is introduced variable, or object, which must belong to one of the data types. The remainder of the chapter is devoted to detailed description each built-in type. In addition, the derived data types for strings and arrays provided by the C++ Standard Library are provided. Although these types are not elementary, they are very important for writing real C++ programs, and we want to introduce the reader to them as early as possible. We will call these data types expansion basic C++ types.

    3.1. Literals

    C++ has a set of built-in data types for representing integer and real numbers, characters, as well as a “character array” data type, which is used to store character strings. The char type is used to store individual characters and small integers. It occupies one machine byte. The types short, int and long are designed to represent integers. These types differ only in the range of values ​​that the numbers can take, and the specific sizes of the listed types are implementation dependent. Typically short takes up half a machine word, int takes one word, long takes one or two words. On 32-bit systems, int and long are usually the same size.

    The float, double, and long double types are for floating-point numbers and differ in their representation precision (number of significant digits) and range. Typically, float (single precision) takes one machine word, double (double precision) takes two, and long double (extended precision) takes three.
    char, short, int and long together make up whole types, which, in turn, can be iconic(signed) and unsigned(unsigned). In signed types, the leftmost bit stores the sign (0 is plus, 1 is minus), and the remaining bits contain the value. In unsigned types, all bits are used for the value. The 8-bit signed char type can represent values ​​from -128 to 127, and the unsigned char can represent values ​​from 0 to 255.

    When a certain number, for example 1, is encountered in a program, this number is called literal, or literal constant. A constant because we cannot change its value, and a literal because its value appears in the program text. A literal is an unaddressable value: although it is, of course, actually stored in the machine's memory, there is no way to know its address. Every literal has a specific type. So, 0 is of type int, 3.14159 is of type double.

    Integer type literals can be written in decimal, octal, and hexadecimal. Here's what the number 20 looks like represented by decimal, octal, and hexadecimal literals:

    20 // decimal
    024 // octal
    0x14 // hexadecimal

    If a literal begins with 0, it is treated as octal, if with 0x or 0X, then as hexadecimal. The usual notation is treated as a decimal number.
    By default, all integer literals are of type signed int. You can explicitly define an entire literal as being of type long by appending the letter L to the end of the number (both uppercase L and lowercase l are used, but for ease of readability you should not use the lowercase: it can easily be confused with

    1). The letter U (or u) at the end identifies the literal as an unsigned int, and two letters - UL or LU - as an unsigned long type. For example:

    128u 1024UL 1L 8Lu

    Literals representing real numbers can be written either with a decimal point or in scientific (scientific) notation. By default they are of type double. To explicitly indicate the float type, you need to use the suffix F or f, and for long double - L or l, but only if written with a decimal point. For example:

    3.14159F 0/1f 12.345L 0.0 3el 1.0E-3E 2. 1.0L

    The words true and false are bool literals.
    Representable literal character constants are written as characters within single quotes. For example:

    "a" "2" "," " " (space)

    Special characters (tab, carriage return) are written as escape sequences. The following such sequences are defined (they begin with a backslash character):

    New line \n horizontal tab \t backspace \b vertical tab \v carriage return \r paper feed \f call \a backslash \\ question \? single quote \" double quote \"

    escape sequence general view has the form \ooo, where ooo is one to three octal digits. This number is the character code. Using ASCII code, we can write the following literals:

    \7 (bell) \14 (new line) \0 (null) \062 ("2")

    A character literal can be prefixed with L (for example, L"a"), which means the special type wchar_t is a two-byte character type that is used to store characters from national alphabets if they cannot be represented by a regular char type, such as Chinese or Japanese letters.
    A string literal is a string of characters enclosed in double quotes. Such a literal can span several lines; in this case, a backslash is placed at the end of the line. Special characters can be represented by their own escape sequences. Here are examples of string literals:

    "" (empty string) "a" "\nCC\toptions\tfile.\n" "a multi-line \ string literal signals its \ continuation with a backslash"

    In fact, a string literal is an array character constants, where, by convention of the C and C++ languages, the last element is always a special character with code 0 (\0).
    The literal "A" specifies a single character A, and the string literal "A" specifies an array of two elements: "A" and \0 (the empty character).
    Since there is a type wchar_t, there are also literals of this type, denoted, as in the case of individual characters, by the prefix L:

    L"a wide string literal"

    A string literal of type wchar_t is a null-terminated array of characters of the same type.
    If two or more string literals (such as char or wchar_t) appear in a row in a program test, the compiler concatenates them into one string. For example, the following text

    "two" "some"

    will produce an array of eight characters - twosome and a terminating null character. Result of string concatenation different types not defined. If you write:

    // this is not a good idea "two" L"some"

    On one computer the result will be some meaningful string, but on another it may be something completely different. Programs that use the implementation features of a particular compiler or operating system, are intolerable. We strongly discourage the use of such structures.

    Exercise 3.1

    Explain the difference in the definitions of the following literals:

    (a) "a", L"a", "a", L"a" (b) 10, 10u, 10L, 10uL, 012, 0*C (c) 3.14, 3.14f, 3.14L

    Exercise 3.2

    What mistakes are made in the examples below?

    (a) "Who goes with F\144rgus?\014" (b) 3.14e1L (c) "two" L"some" (d) 1024f (e) 3.14UL (f) "multiple line comment"

    3.2. Variables

    Let's imagine that we are solving the problem of raising 2 to the power of 10. We write:

    #include
    int main() (
    // a first solution
    cout<< "2 raised to the power of 10: ";
    cout<< 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2;
    cout<< endl;
    return 0;
    }

    The problem was solved, although we had to repeatedly check whether the literal 2 was actually repeated 10 times. We did not make a mistake in writing this long sequence of twos, and the program produced the correct result - 1024.
    But now we were asked to raise 2 to the 17th power, and then to the 23rd power. It is extremely inconvenient to modify the program text every time! And, even worse, it is very easy to make a mistake by writing an extra two or omitting it... But what if you need to print a table of powers of two from 0 to 15? Repeat two lines that have the general form 16 times:

    Cout<< "2 в степени X\t"; cout << 2 * ... * 2;

    where X is successively increased by 1, and the required number of literals is substituted for the decimal?

    Yes, we coped with the task. The customer is unlikely to delve into the details, being satisfied with the result obtained. In real life, this approach often works; moreover, it is justified: the problem is solved in a far from elegant way, but in the desired time frame. Looking for a more beautiful and competent option may turn out to be an impractical waste of time.

    In this case, the “brute force method” gives the correct answer, but how unpleasant and boring it is to solve the problem in this way! We know exactly what steps need to be taken, but these steps themselves are simple and monotonous.

    Involving more complex mechanisms for the same task, as a rule, significantly increases the time of the preparatory stage. In addition, the more complex mechanisms are used, the greater the likelihood of errors. But even despite the inevitable mistakes and wrong moves, the use of “high technologies” can bring benefits in development speed, not to mention the fact that these technologies significantly expand our capabilities. And – what’s interesting! – the decision process itself can become attractive.
    Let's return to our example and try to “technologically improve” its implementation. We can use a named object to store the value of the power to which we want to raise our number. In addition, instead of a repeating sequence of literals, a loop operator can be used. This is what it will look like:

    #include
    int main()
    {
    // objects of type int
    int value = 2;
    int pow = 10;
    cout<< value << " в степени "
    << pow << ": \t";
    int res = 1;
    // loop operator:
    // repeat res calculation
    // until cnt becomes greater than pow
    for (int cnt=1; cnt<= pow; ++cnt)
    res = res * value;
    cout<< res << endl;
    }

    value, pow, res and cnt are variables that allow you to store, modify and retrieve values. The for loop statement repeats the result line pow times.
    There is no doubt that we have created a much more flexible program. However, this is still not a function. To get a real function that can be used in any program to calculate the power of a number, you need to select the general part of the calculations, and set specific values ​​​​with parameters.

    Int pow(int val, int exp) ( for (int res = 1; exp > 0; --exp) res = res * val; return res; )

    Now it will not be difficult to obtain any power of the desired number. Here's how our last task is implemented - print a table of powers of two from 0 to 15:

    #include extern int pow(int,int); int main() ( int val = 2; int exp = 15;
    cout<< "Степени 2\n";
    for (int cnt=0; cnt<= exp; ++cnt)
    cout<< cnt << ": "
    << pow(val, cnt) << endl;
    return 0;
    }

    Of course, our pow() function is still not general enough and not robust enough. It cannot operate with real numbers, it incorrectly raises numbers to a negative power - it always returns 1. The result of raising a large number to a higher power may not fit into an int variable, and then some random incorrect value will be returned. Do you see how difficult it turns out to be to write functions designed for widespread use? Much more difficult than implementing a specific algorithm aimed at solving a specific problem.

    3.2.1. What is a variable

    Variable, or object– this is a named area of ​​​​memory that we have access to from the program; You can put values ​​there and then retrieve them. Every C++ variable has a specific type, which characterizes the size and location of that memory location, the range of values ​​it can store, and the set of operations that can be applied to that variable. Here is an example of defining five objects of different types:

    Int student_count; double salary; bool on_loan; strins street_address; char delimiter;

    A variable, like a literal, has a specific type and stores its value in some area of ​​​​memory. Addressability- that's what the literal lacks. There are two quantities associated with a variable:

    • the actual value, or r-value (from read value - value for reading), which is stored in this memory area and is inherent in both the variable and the literal;
    • the address value of the memory area associated with the variable, or l-value (from location value) - the place where the r-value is stored; inherent only in the object.

    In expression

    Ch = ch - "0";

    the variable ch is located both to the left and to the right of the assignment symbol. On the right is the value to read (ch and the character literal "0"): the data associated with the variable is read from the corresponding memory area. On the left is the location value: the result of the subtraction is placed in the memory area associated with the variable ch. In general, the left operand of an assignment operation must be an l-value. We cannot write the following expressions:

    // compilation errors: values ​​on the left are not l-values ​​// error: literal is not an l-value 0 = 1; // error: arithmetic expression is not an l-value salary + salary * 0.10 = new_salary;

    The variable definition statement allocates memory for it. Since an object has only one memory location associated with it, such a statement can only appear once in a program. If a variable defined in one source file must be used in another, problems arise. For example:

    // file module0.C // defines an object fileName string fileName; // ... assign fileName a value
    // file module1.C
    // uses the fileName object
    // alas, does not compile:
    // fileName is not defined in module1.C
    ifstream input_file(fileName);

    C++ requires that an object be known before it is first accessed. This is necessary to ensure that the object is used correctly according to its type. In our example, module1.C will cause a compilation error because the fileName variable is not defined in it. To avoid this error, we must tell the compiler about the already defined fileName variable. This is done using a variable declaration statement:

    // file module1.C // uses the fileName object // fileName is declared, that is, the program receives
    // information about this object without its secondary definition
    extern string fileName; ifstream input_file(fileName)

    A variable declaration tells the compiler that an object with a given name, of a given type, is defined somewhere in the program. Memory is not allocated for a variable when it is declared. (The extern keyword is covered in Section 8.2.)
    A program can contain as many declarations of the same variable as it likes, but it can only be defined once. It is convenient to place such declarations in header files, including them in those modules that require it. This way we can store information about objects in one place and make it easy to modify if necessary. (We'll talk more about header files in Section 8.2.)

    3.2.2. Variable name

    Variable name, or identifier, can consist of Latin letters, numbers and the underscore character. Uppercase and lowercase letters in names are different. The C++ language does not limit the length of the identifier, but using too long names like gosh_this_is_an_impossibly_name_to_type is inconvenient.
    Some words are keywords in C++ and cannot be used as identifiers; Table 3.1 provides a complete list of them.

    Table 3.1. C++ Keywords

    asm auto bool break case
    catch char class const const_cast
    continue default delete do double
    dynamic_cast else enum explicit export
    extern false float for friend
    goto if inline int long
    mutable namespace new operator private
    protected public register reinterpret_cast return
    short signed sizeof static static_cast
    struct switch template this throw
    typedef true try typeid typename
    union void union using virtual void

    To make your program more understandable, we recommend following generally accepted object naming conventions:

    • the variable name is usually written in lowercase letters, for example index (for comparison: Index is the name of the type, and INDEX is a constant defined using the #define preprocessor directive);
    • the identifier must have some meaning, explaining the purpose of the object in the program, for example: birth_date or salary;

    If such a name consists of several words, such as birth_date, then it is customary to either separate the words with an underscore (birth_date) or write each subsequent word with a capital letter (birthDate). It has been noticed that programmers accustomed to the Object Oriented Approach prefer to highlight words in capital letters, while those_who_have_written_a lot_in_C use the underscore character. Which of the two methods is better is a matter of taste.

    3.2.3. Object Definition

    In its simplest case, the object definition operator consists of type specifier And object name and ends with a semicolon. For example:

    Double salary double wage; int month; int day; int year; unsigned long distance;

    You can define multiple objects of the same type in one statement. In this case, their names are listed separated by commas:

    Double salary, wage; int month, day, year; unsigned long distance;

    Simply defining a variable does not give it an initial value. If an object is defined as global, the C++ specification guarantees that it will be initialized to zero. If the variable is local or dynamically allocated (using the new operator), its initial value is undefined, that is, it may contain some random value.
    The use of such variables is a very common mistake, which is also difficult to detect. It is recommended to explicitly specify the initial value of an object, at least in cases where it is not known whether the object can initialize itself. The class mechanism introduces the concept of a default constructor, which is used to assign default values. (We already talked about this in Section 2.3. We'll talk about default constructors a little later, in Sections 3.11 and 3.15, where we'll look at the string and complex classes from the standard library.)

    Int main() ( // uninitialized local object int ival;
    // object of type string is initialized
    // default constructor
    string project;
    // ...
    }

    The initial value can be specified directly in the variable definition statement. In C++, two forms of variable initialization are allowed - explicit, using the assignment operator:

    Int ival = 1024; string project = "Fantasia 2000";

    and implicit, with the initial value specified in parentheses:

    Int ival(1024); string project("Fantasia 2000");

    Both options are equivalent and set the initial values ​​for the integer variable ival to 1024 and for the project string to "Fantasia 2000".
    Explicit initialization can also be used when defining variables in a list:

    Double salary = 9999.99, wage = salary + 0.01; int month = 08; day = 07, year = 1955;

    The variable becomes visible (and valid in the program) immediately after it is defined, so we could initialize the wage variable with the sum of the newly defined salary variable with some constant. So the definition is:

    // correct, but meaningless int bizarre = bizarre;

    is syntactically valid, although meaningless.
    Built-in data types have a special syntax for specifying a null value:

    // ival gets the value 0 and dval gets 0.0 int ival = int(); double dval = double();

    In the following definition:

    // int() is applied to each of the 10 elements of the vector< int >ivec(10);

    Each of the ten elements of the vector is initialized with int(). (We already talked about the vector class in Section 2.8. For more on this, see Section 3.10 and Chapter 6.)
    The variable can be initialized with an expression of any complexity, including function calls. For example:

    #include #include
    double price = 109.99, discount = 0.16;
    double sale_price(price * discount);
    string pet("wrinkles"); extern int get_value(); int val = get_value();
    unsigned abs_val = abs(val);

    abs() is a standard function that returns the absolute value of a parameter.
    get_value() is some user-defined function that returns an integer value.

    Exercise 3.3

    Which of the following variable definitions contain syntax errors?

    (a) int car = 1024, auto = 2048; (b) int ival = ival; (c) int ival(int()); (d) double salary = wage = 9999.99; (e) cin >> int input_value;

    Exercise 3.4

    Explain the difference between l-value and r-value. Give examples.

    Exercise 3.5

    Find the differences in the use of the name and student variables in the first and second lines of each example:

    (a) extern string name; string name("exercise 3.5a"); (b) extern vector students; vector students;

    Exercise 3.6

    What object names are invalid in C++? Change them so that they are syntactically correct:

    (a) int double = 3.14159; (b)vector< int >_; (c) string namespace; (d) string catch-22; (e) char 1_or_2 = "1"; (f) float Float = 3.14f;

    Exercise 3.7

    What is the difference between the following global and local variable definitions?

    String global_class; int global_int; int main() (
    int local_int;
    string local_class; // ...
    }

    3.3. Signposts

    Pointers and dynamic memory allocation were briefly introduced in Section 2.2. Pointer is an object that contains the address of another object and allows indirect manipulation of this object. Typically, pointers are used to manipulate dynamically created objects, to construct related data structures such as linked lists and hierarchical trees, and to pass large objects—arrays and class objects—as parameters to functions.
    Each pointer is associated with a certain data type, and their internal representation does not depend on the internal type: both the memory size occupied by an object of the pointer type and the range of values ​​are the same. The difference is how the compiler treats the addressed object. Pointers to different types can have the same value, but the memory area where the corresponding types are located can be different:

    • a pointer to int containing the address value 1000 is directed to memory area 1000-1003 (on a 32-bit system);
    • a pointer to double containing the value of address 1000 is directed to memory area 1000-1007 (on a 32-bit system).

    Here are some examples:

    Int *ip1, *ip2; complex *cp; string *pstring; vector *pvec; double *dp;

    The index is indicated by an asterisk before the name. In defining variables with a list, an asterisk must precede each pointer (see above: ip1 and ip2). In the example below, lp is a pointer to a long object, and lp2 is a long object:

    Long *lp, lp2;

    In the following case, fp is interpreted as a float object, and fp2 is a pointer to it:

    Float fp, *fp2;

    The dereference operator (*) can be separated by spaces from the name and even directly adjacent to the type keyword. Therefore, the above definitions are syntactically correct and completely equivalent:

    //attention: ps2 is not a pointer to a string! string* ps, ps2;

    It can be assumed that both ps and ps2 are pointers, although a pointer is only the first of them.
    If the pointer value is 0, then it does not contain any object address.
    Let a variable of type int be specified:

    Int ival = 1024;

    The following are examples of defining and using pointers to int pi and pi2:

    //pi is initialized to zero address int *pi = 0;
    // pi2 is initialized with ival address
    int *pi2 =
    // correct: pi and pi2 contain the address ival
    pi = pi2;
    // pi2 contains address zero
    pi2 = 0;

    A pointer cannot be assigned a value that is not an address:

    // error: pi cannot accept the value int pi = ival

    In the same way, you cannot assign a value to a pointer of one type that is the address of an object of another type. If the following variables are defined:

    Double dval; double *ps =

    then both assignment expressions below will cause a compilation error:

    // compilation errors // invalid data type assignment: int*<== double* pi = pd pi = &dval;

    It's not that the pi variable cannot contain the addresses of a dval object - the addresses of objects of different types have the same length. Such address mixing operations are deliberately prohibited because the compiler's interpretation of objects depends on the type of the pointer to them.
    Of course, there are cases when we are interested in the value of the address itself, and not in the object to which it points (let's say we want to compare this address with some other one). To resolve such situations, a special void pointer has been introduced, which can point to any data type, and the following expressions will be correct:

    // correct: void* can contain // addresses of any type void *pv = pi; pv = pd;

    The type of the object pointed to by void* is unknown and we cannot manipulate this object. All we can do with such a pointer is assign its value to another pointer or compare it with some address value. (We'll talk more about the void pointer in Section 4.14.)
    In order to access an object given its address, you need to use the dereferencing operation, or indirect addressing, denoted by an asterisk (*). Having the following variable definitions:

    Int ival = 1024;, ival2 = 2048; int *pi =

    // indirect assignment of the variable ival to the value ival2 *pi = ival2;
    // indirect use of ival variable as rvalue and lvalue
    *pi = abs(*pi); // ival = abs(ival);
    *pi = *pi + 1; // ival = ival + 1;

    When we apply the address operator (&) to an object of type int, we get a result of type int*
    int *pi =
    If we apply the same operation to an object of type int* (pointer to int), we get a pointer to a pointer to int, i.e. int**. int** is the address of an object that contains the address of an object of type int. By dereferencing ppi, we get an object of type int* containing the address of ival. To obtain the ival object itself, the dereference operation on ppi must be applied twice.

    Int **ppi = π int *pi2 = *ppi;
    cout<< "Значение ival\n" << "явное значение: " << ival << "\n"
    << "косвенная адресация: " << *pi << "\n"
    << "дважды косвенная адресация: " << **ppi << "\n"

    Pointers can be used in arithmetic expressions. Notice the following example, where two expressions do completely different things:

    Int i, j, k; int *pi = // i = i + 2
    *pi = *pi + 2; //increase the address contained in pi by 2
    pi = pi + 2;

    You can add an integer value to a pointer, or you can subtract from it. Adding 1 to a pointer increases the value it contains by the size of the memory allocated to an object of the corresponding type. If char is 1 byte, int is 4 bytes, and double is 8, then adding 2 to the pointers to char, int, and double will increase their value by 2, 8, and 16, respectively. How can this be interpreted? If objects of the same type are located one after another in memory, then increasing the pointer by 1 will cause it to point to the next object. Therefore, pointer arithmetic is most often used when processing arrays; in any other cases they are hardly justified.
    Here's what a typical example of using address arithmetic looks like when iterating over the elements of an array using an iterator:

    Int ia; int *iter = int *iter_end =
    while (iter != iter_end) (
    do_something_with_value (*iter);
    ++iter;
    }

    Exercise 3.8

    The definitions of the variables are given:

    Int ival = 1024, ival2 = 2048; int *pi1 = &ival, *pi2 = &ival2, **pi3 = 0;

    What happens when you perform the following assignment operations? Are there any mistakes in these examples?

    (a) ival = *pi3; (e) pi1 = *pi3; (b) *pi2 = *pi3; (f) ival = *pi1; (c) ival = pi2; (g) pi1 = ival; (d) pi2 = *pi1; (h)pi3 =

    Exercise 3.9

    Working with pointers is one of the most important aspects of C and C++, but it's easy to make mistakes. For example, code

    Pi = pi = pi + 1024;

    will almost certainly cause pi to point to a random memory location. What does this assignment operator do and when will it not cause an error?

    Exercise 3.10

    This program contains an error related to incorrect use of pointers:

    Int foobar(int *pi) ( *pi = 1024; return *pi; )
    int main() (
    int *pi2 = 0;
    int ival = foobar(pi2);
    return 0;
    }

    What is the error? How can I fix it?

    Exercise 3.11

    The errors from the previous two exercises manifest themselves and lead to fatal consequences due to C++'s lack of checking for the correctness of pointer values ​​during program execution. Why do you think such a check was not implemented? Can you offer some general guidelines to make working with pointers more secure?

    3.4. String types

    C++ supports two types of strings - a built-in type inherited from C, and the string class from the C++ standard library. The string class provides much more capabilities and is therefore more convenient to use, however, in practice there are often situations when it is necessary to use a built-in type or have a good understanding of how it works. (One example would be parsing the command line parameters passed to the main() function. We'll cover this in Chapter 7.)

    3.4.1. Built-in string type

    As already mentioned, the built-in string type came to C++ as a legacy of C. The character string is stored in memory as an array and accessed using a pointer type char*. The C standard library provides a set of functions for manipulating strings. For example:

    // returns the length of the string int strlen(const char*);
    // compares two strings
    int strcmp(const char*, const char*);
    // copies one line to another
    char* strcpy(char*, const char*);

    The C standard library is part of the C++ library. To use it we must include the header file:

    #include

    The pointer to char, which we use to access a string, points to the array of characters corresponding to the string. Even when we write a string literal like

    Const char *st = "Price of a bottle of wine\n";

    the compiler puts all the characters of the string into an array and then assigns st the address of the first element of the array. How can you work with a string using such a pointer?
    Typically, address arithmetic is used to iterate over characters in a string. Since the string always ends with a null character, you can increment the pointer by 1 until the next character becomes a null. For example:

    While (*st++) ( ... )

    st is dereferenced and the resulting value is checked to see if it is true. Any non-zero value is considered true, and hence the loop ends when a character with code 0 is reached. The increment operator ++ adds 1 to the pointer st and thus shifts it to the next character.
    This is what an implementation of a function that returns the length of a string might look like. Note that since a pointer can contain a null value (not point to anything), it should be checked before the dereference operation:

    Int string_length(const char *st) ( int cnt = 0; if (st) while (*st++) ++cnt; return cnt; )

    A built-in string can be considered empty in two cases: if the pointer to the string has a null value (in which case we have no string at all) or if it points to an array consisting of a single null character (that is, to a string that does not contain any significant characters).

    // pc1 does not address any character array char *pc1 = 0; // pc2 addresses the null character const char *pc2 = "";

    For a novice programmer, using built-in type strings is fraught with errors due to the too low level of implementation and the inability to do without address arithmetic. Below we will show some common mistakes made by beginners. The task is simple: calculate the length of the string. The first version is incorrect. Fix her.

    #include const char *st = "Price of a bottle of wine\n"; int main() (
    int len ​​= 0;
    while (st++) ++len; cout<< len << ": " << st;
    return 0;
    }

    In this version, the st pointer is not dereferenced. Therefore, it is not the character pointed to by st that is checked for equality 0, but the pointer itself. Since this pointer initially had a non-zero value (the address of the string), it will never become zero, and the loop will run endlessly.
    In the second version of the program this error has been eliminated. The program finishes successfully, but the output is incorrect. Where are we going wrong this time?

    #include const char *st = "Price of a bottle of wine\n"; int main()
    {
    int len ​​= 0;
    while (*st++) ++len; cout<< len << ": " << st << endl;
    return 0;
    }

    The error is that after the loop ends, the st pointer does not address the original character literal, but the character located in memory after the terminating null of that literal. Anything can be in this place, and the program's output will be a random sequence of characters.
    You can try to fix this error:

    St = st – len; cout<< len << ": " << st;

    Now our program produces something meaningful, but not completely. The answer looks like this:

    18: price of wine bottle

    We forgot to take into account that the trailing null character was not included in the calculated length. st must be offset by the length of the string plus 1. Here is finally the correct operator:

    St = st – len - 1;

    and here is the correct result:

    18: Price of a bottle of wine

    However, we cannot say that our program looks elegant. Operator

    St = st – len - 1;

    added in order to correct a mistake made at an early stage of program design - directly increasing the st pointer. This statement does not fit into the logic of the program, and the code is now difficult to understand. These kinds of fixes are often called patches—something designed to plug a hole in an existing program. A much better solution would be to reconsider the logic. One option in our case would be to define a second pointer initialized with the value st:

    Const char *p = st;

    Now p can be used in a length calculation loop, leaving the value of st unchanged:

    While (*p++)

    3.4.2. String class

    As we just saw, using the built-in string type is error prone and inconvenient because it is implemented at such a low level. Therefore, it was quite common to develop your own class or classes to represent a string type - almost every company, department, or individual project had its own implementation of a string. What can I say, in the previous two editions of this book we did the same thing! This gave rise to problems of program compatibility and portability. The implementation of the standard string class by the C++ standard library was intended to put an end to this reinvention of bicycles.
    Let's try to specify the minimum set of operations that the string class should have:

    • initialization with an array of characters (a built-in string type) or another object of type string. A built-in type does not have the second capability;
    • copying one line to another. For a built-in type you have to use the strcpy() function;
    • access to individual characters of a string for reading and writing. In a built-in array, this is done using an index operation or indirect addressing;
    • comparing two strings for equality. For a built-in type, use the strcmp() function;
    • concatenation of two strings, producing the result either as a third string or instead of one of the original ones. For a built-in type, the strcat() function is used, but to get the result in a new line, you need to use the strcpy() and strcat() functions sequentially;
    • calculating the length of a string. You can find out the length of a built-in type string using the strlen() function;
    • ability to find out if a string is empty. For built-in strings, two conditions have to be checked for this purpose: char str = 0; //... if (! str || ! *str) return;

    The C++ Standard Library's string class implements all of these operations (and much more, as we'll see in Chapter 6). In this section we will learn how to use the basic operations of this class.
    In order to use objects of the string class, you must include the corresponding header file:

    #include

    Here is an example of a string from the previous section, represented by an object of type string and initialized to a character string:

    #include string st("Price of a bottle of wine\n");

    The length of the string is returned by the size() member function (the length does not include the terminating null character).

    Cout<< "Длина " << st << ": " << st.size() << " символов, включая символ новой строки\n";

    The second form of a string definition specifies an empty string:

    String st2; // empty string

    How do we know if a string is empty? Of course, you can compare its length with 0:

    If (! st.size()) // correct: empty

    However, there is also a special method empty() that returns true for an empty string and false for a non-empty one:

    If (st.empty()) // correct: empty

    The third form of the constructor initializes an object of type string with another object of the same type:

    String st3(st);

    The string st3 is initialized with the string st. How can we make sure these strings match? Let's use the comparison operator (==):

    If (st == st3) // initialization worked

    How to copy one line to another? Using the normal assignment operator:

    St2 = st3; // copy st3 to st2

    To concatenate strings, use the addition (+) or addition with assignment operation (+=). Let two lines be given:

    String s1("hello, "); string s2("world\n");

    We can get a third string consisting of a concatenation of the first two, this way:

    String s3 = s1 + s2;

    If we want to add s2 to the end of s1, we should write:

    S1 += s2;

    The addition operation can concatenate objects of the string class not only with each other, but also with strings of the built-in type. You can rewrite the example above so that special characters and punctuation marks are represented by a built-in type, and meaningful words are represented by objects of class string:

    Const char *pc = ", "; string s1("hello"); string s2("world");
    string s3 = s1 + pc + s2 + "\n";

    Such expressions work because the compiler knows how to automatically convert objects of the built-in type to objects of the string class. It is also possible to simply assign a built-in string to a string object:

    String s1; const char *pc = "a character array"; s1 = pc; // Right

    The reverse conversion, however, does not work. Attempting to perform the following built-in type string initialization will cause a compilation error:

    Char *str = s1; // compilation error

    To accomplish this conversion, you must explicitly call the somewhat strangely named c_str() member function:

    Char *str = s1.c_str(); // almost correct

    The c_str() function returns a pointer to a character array containing the string object's string as it would appear in the built-in string type.
    The above example of initializing a char *str pointer is still not entirely correct. c_str() returns a pointer to a constant array to prevent the object's contents from being directly modified by this pointer, which has type

    Const char *

    (We'll cover the const keyword in the next section.) The correct initialization option looks like this:

    Const char *str = s1.c_str(); // Right

    The individual characters of a string object, like a built-in type, can be accessed using the index operation. For example, here is a piece of code that replaces all periods with underscores:

    String str("fa.disney.com"); int size = str.size(); for (int ix = 0; ix< size; ++ix) if (str[ ix ] == ".")
    str[ ix ] = "_";

    That's all we wanted to say about the string class right now. In fact, this class has many more interesting properties and capabilities. Let's say the previous example is also implemented by calling a single replace() function:

    Replace(str.begin(), str.end(), ".", "_");

    replace() is one of the general algorithms that we introduced in Section 2.8 and which will be discussed in detail in Chapter 12. This function runs through the range from begin() to end(), which return pointers to the beginning and end of the string, and replaces the elements , equal to its third parameter, to the fourth.

    Exercise 3.12

    Find errors in the statements below:

    (a) char ch = "The long and winding road"; (b) int ival = (c) char *pc = (d) string st(&ch); (e) pc = 0; (i) pc = "0";
    (f) st = pc; (j) st =
    (g) ch = pc; (k) ch = *pc;
    (h) pc = st; (l) *pc = ival;

    Exercise 3.13

    Explain the difference in behavior of the following loop statements:

    While (st++) ++cnt;
    while (*st++)
    ++cnt;

    Exercise 3.14

    Two semantically equivalent programs are given. The first uses the built-in string type, the second uses the string class:

    // ***** Implementation using C strings ***** #include #include
    int main()
    {
    int errors = 0;
    const char *pc = "a very long literal string"; for (int ix = 0; ix< 1000000; ++ix)
    {
    int len ​​= strlen(pc);
    char *pc2 = new char[ len + 1 ];
    strcpy(pc2, pc);
    if (strcmp(pc2, pc))
    ++errors; delete pc2;
    }
    cout<< "C-строки: "
    << errors << " ошибок.\n";
    ) // ***** Implementation using the string class ***** #include
    #include
    int main()
    {
    int errors = 0;
    string str("a very long literal string"); for (int ix = 0; ix< 1000000; ++ix)
    {
    int len ​​= str.size();
    string str2 = str;
    if (str != str2)
    }
    cout<< "класс string: "
    << errors << " ошибок.\n;
    }

    What do these programs do?
    It turns out that the second implementation runs twice as fast as the first. Did you expect such a result? How do you explain it?

    Exercise 3.15

    Could you improve or add anything to the set of operations of the string class given in the last section? Explain your proposals

    3.5. const specifier

    Let's take the following code example:

    For (int index = 0; index< 512; ++index) ... ;

    There are two problems with using the 512 literal. The first is the ease of perception of the program text. Why should the upper bound of the loop variable be exactly 512? What is hidden behind this value? She seems random...
    The second problem concerns the ease of modification and maintenance of the code. Let's say the program has 10,000 lines and the literal 512 occurs in 4% of them. Let's say that in 80% of cases the number 512 must be changed to 1024. Can you imagine the complexity of such work and the number of mistakes that can be made by correcting the wrong value?
    We solve both of these problems at the same time: we need to create an object with the value 512. Giving it a meaningful name, such as bufSize, we make the program much more understandable: it is clear what exactly the loop variable is being compared to.

    Index< bufSize

    In this case, changing the size of bufSize does not require going through 400 lines of code to modify 320 of them. How much the likelihood of errors is reduced by adding just one object! Now the value is 512 localized.

    Int bufSize = 512; // input buffer size // ... for (int index = 0; index< bufSize; ++index)
    // ...

    One small problem remains: the bufSize variable here is an l-value that can be accidentally changed in the program, leading to a hard-to-catch error. Here's one common mistake: using the assignment operator (=) instead of the comparison operator (==):

    // random change in bufSize value if (bufSize = 1) // ...

    Executing this code will cause the bufSize value to be 1, which can lead to completely unpredictable program behavior. Errors of this kind are usually very difficult to detect because they are simply not visible.
    Using the const specifier solves this problem. By declaring the object as

    Const int bufSize = 512; // input buffer size

    we turn the variable into a constant with value 512, the value of which cannot be changed: such attempts are suppressed by the compiler: incorrect use of the assignment operator instead of comparison, as in the example above, will cause a compilation error.

    // error: attempt to assign a value to a constant if (bufSize = 0) ...

    Since a constant cannot be assigned a value, it must be initialized at the place where it is defined. Defining a constant without initializing it also causes a compilation error:

    Const double pi; // error: uninitialized constant

    Const double minWage = 9.60; // Right? error?
    double *ptr =

    Should the compiler allow such an assignment? Since minWage is a constant, it cannot be assigned a value. On the other hand, nothing prevents us from writing:

    *ptr += 1.40; // changing the minWage object!

    As a rule, the compiler is not able to protect against the use of pointers and will not be able to signal an error if they are used in this way. This requires too deep an analysis of the program logic. Therefore, the compiler simply prohibits the assignment of constant addresses to ordinary pointers.
    So, are we deprived of the ability to use pointers to constants? No. For this purpose, there are pointers declared with the const specifier:

    Const double *cptr;

    where cptr is a pointer to an object of type const double. The subtlety is that the pointer itself is not a constant, which means we can change its value. For example:

    Const double *pc = 0; const double minWage = 9.60; // correct: we cannot change minWage using pc
    pc = double dval = 3.14; // correct: we cannot change minWage using pc
    // although dval is not a constant
    pc = // correct dval = 3.14159; //Right
    *pc = 3.14159; // error

    The address of a constant object is assigned only to a pointer to a constant. At the same time, such a pointer can also be assigned the address of a regular variable:

    Pc =

    A constant pointer does not allow the object it addresses to be modified using indirect addressing. Although dval in the example above is not a constant, the compiler will not allow dval to be changed via pc. (Again, because it is not able to determine which object's address may contain a pointer at any time during program execution.)
    In real programs, pointers to constants are most often used as formal parameters of functions. Their use ensures that the object passed to a function as an actual argument will not be modified by that function. For example:

    // In real programs, pointers to constants are most often // used as formal parameters of functions int strcmp(const char *str1, const char *str2);

    (We'll talk more about constant pointers in Chapter 7, when we talk about functions.)
    There are also constant pointers. (Note the difference between a const pointer and a pointer to a constant!). A const pointer can address either a constant or a variable. For example:

    Int errNumb = 0; int *const currErr =

    Here curErr is a const pointer to a non-const object. This means that we cannot assign it the address of another object, although the object itself can be modified. Here's how the curErr pointer could be used:

    Do_something(); if (*curErr) (
    errorHandler();
    *curErr = 0; // correct: reset errNumb value
    }

    Trying to assign a value to a const pointer will cause a compilation error:

    CurErr = // error

    A constant pointer to a constant is a union of the two cases considered.

    Const double pi = 3.14159; const double *const pi_ptr = π

    Neither the value of the object pointed to by pi_ptr nor the value of the pointer itself can be changed in the program.

    Exercise 3.16

    Explain the meaning of the following five definitions. Are any of them wrong?

    (a) int i; (d) int *const cpi; (b) const int ic; (e) const int *const cpic; (c) const int *pic;

    Exercise 3.17

    Which of the following definitions are correct? Why?

    (a) int i = -1; (b) const int ic = i; (c) const int *pic = (d) int *const cpi = (e) const int *const cpic =

    Exercise 3.18

    Using the definitions from the previous exercise, identify the correct assignment operators. Explain.

    (a) i = ic; (d) pic = cpic; (b) pic = (i) cpic = (c) cpi = pic; (f) ic = *cpic;

    3.6. Reference type

    A reference type, sometimes called an alias, is used to give an object an additional name. A reference allows you to manipulate an object indirectly, just like you can with a pointer. However, this indirect manipulation does not require the special syntax required for pointers. Typically, references are used as formal parameters of functions. In this section, we will look at using reference type objects on our own.
    A reference type is indicated by specifying the address operator (&) before the variable name. The link must be initialized. For example:

    Int ival = 1024; // correct: refVal - reference to ival int &refVal = ival; // error: reference must be initialized to int

    Int ival = 1024; // error: refVal is of type int, not int* int &refVal = int *pi = // correct: ptrVal is a reference to a pointer int *&ptrVal2 = pi;

    Once a reference is defined, you can't change it to work with another object (which is why the reference must be initialized at the point where it is defined). In the following example, the assignment operator does not change the value of refVal; the new value is assigned to the variable ival - the one that refVal addresses.

    Int min_val = 0; // ival receives the value of min_val, // instead of refVal changes the value to min_val refVal = min_val;

    RefVal += 2; adds 2 to ival, the variable referenced by refVal. Similarly int ii = refVal; assigns ii the current value of ival, int *pi = initializes pi with the address of ival.

    // two objects of type int are defined int ival = 1024, ival2 = 2048; // one reference and one object defined int &rval = ival, rval2 = ival2; // one object, one pointer and one reference are defined
    int inal3 = 1024, *pi = ival3, &ri = ival3; // two links are defined int &rval3 = ival3, &rval4 = ival2;

    A const reference can be initialized by an object of another type (assuming, of course, it is possible to convert one type to another), as well as by an addressless value such as a literal constant. For example:

    Double dval = 3.14159; // only true for constant references
    const int &ir = 1024;
    const int &ir2 = dval;
    const double &dr = dval + 1.0;

    If we had not specified the const specifier, all three reference definitions would have generated a compilation error. However, the reason why the compiler does not pass such definitions is unclear. Let's try to figure it out.
    For literals, this is more or less clear: we should not be able to indirectly change the value of a literal using pointers or references. As for objects of other types, the compiler converts the original object into some auxiliary object. For example, if we write:

    Double dval = 1024; const int &ri = dval;

    then the compiler converts it to something like this:

    Int temp = dval; const int &ri = temp;

    If we could assign a new value to the ri reference, we would actually change not dval, but temp. The dval value would remain the same, which is completely unobvious to the programmer. Therefore, the compiler prohibits such actions, and the only way to initialize a reference with an object of another type is to declare it as const.
    Here is another example of a link that is difficult to understand the first time. We want to define a reference to the address of a constant object, but our first option causes a compilation error:

    Const int ival = 1024; // error: constant reference needed
    int *&pi_ref =

    An attempt to correct the matter by adding a const specifier also fails:

    Const int ival = 1024; // still an error const int *&pi_ref =

    What is the reason? If we read the definition carefully, we will see that pi_ref is a reference to a constant pointer to an object of type int. And we need a non-const pointer to a constant object, so the following entry would be correct:

    Const int ival = 1024; // Right
    int *const &piref =

    There are two main differences between a link and a pointer. First, the link must be initialized at the place where it is defined. Secondly, any change to a link transforms not the link, but the object to which it refers. Let's look at examples. If we write:

    Int *pi = 0;

    we initialize the pointer pi to zero, which means that pi does not point to any object. At the same time recording

    const int &ri = 0;
    means something like this:
    int temp = 0;
    const int &ri = temp;

    As for the assignment operation, in the following example:

    Int ival = 1024, ival2 = 2048; int *pi = &ival, *pi2 = pi = pi2;

    the variable ival pointed to by pi remains unchanged, and pi receives the value of the address of the variable ival2. Both pi and pi2 now point to the same object, ival2.
    If we work with links:

    Int &ri = ival, &ri2 = ival2; ri = ri2;

    // example of using links // The value is returned in the next_value parameter
    bool get_next_value(int &next_value); // overloaded operator Matrix operator+(const Matrix&, const Matrix&);

    Int ival; while (get_next_value(ival)) ...

    Int &next_value = ival;

    (The use of references as formal parameters of functions is discussed in more detail in Chapter 7.)

    Exercise 3.19

    Are there any errors in these definitions? Explain. How would you fix them?

    (a) int ival = 1.01; (b) int &rval1 = 1.01; (c) int &rval2 = ival; (d) int &rval3 = (e) int *pi = (f) int &rval4 = pi; (g) int &rval5 = pi*; (h) int &*prval1 = pi; (i) const int &ival2 = 1; (j) const int &*prval2 =

    Exercise 3.20

    Are any of the following assignment operators erroneous (using the definitions from the previous exercise)?

    (a) rval1 = 3.14159; (b) prval1 = prval2; (c) prval2 = rval1; (d) *prval2 = ival2;

    Exercise 3.21

    Find errors in the given instructions:

    (a) int ival = 0; const int *pi = 0; const int &ri = 0; (b)pi =
    ri =
    pi =

    3.7. Type bool

    An object of type bool can take one of two values: true and false. For example:

    // initialization of the string string search_word = get_word(); // initialization of the found variable
    bool found = false; string next_word; while (cin >> next_word)
    if (next_word == search_word)
    found = true;
    // ... // shorthand: if (found == true)
    if (found)
    cout<< "ok, мы нашли слово\n";
    else cout<< "нет, наше слово не встретилось.\n";

    Although bool is one of the integer types, it cannot be declared as signed, unsigned, short or long, so the above definition is erroneous:

    // error short bool found = false;

    Objects of type bool are implicitly converted to type int. True becomes 1 and false becomes 0. For example:

    Bool found = false; int occurrence_count = 0; while (/* mumble */)
    {
    found = look_for(/* something */); // value found is converted to 0 or 1
    occurrence_count += found; )

    In the same way, values ​​of integer types and pointers can be converted to values ​​of type bool. In this case, 0 is interpreted as false, and everything else as true:

    // returns the number of occurrences extern int find(const string&); bool found = false; if (found = find("rosebud")) // correct: found == true // returns a pointer to the element
    extern int* find(int value); if (found = find(1024)) // correct: found == true

    3.8. Transfers

    Often you have to define a variable that takes values ​​from a certain set. Let's say a file is opened in any of three modes: for reading, for writing, for appending.
    Of course, three constants can be defined to denote these modes:

    Const int input = 1; const int output = 2; const int append = 3;

    and use these constants:

    Bool open_file(string file_name, int open_mode); // ...
    open_file("Phoenix_and_the_Crane", append);

    This solution is possible, but not entirely acceptable, since we cannot guarantee that the argument passed to the open_file() function is only 1, 2 or 3.
    Using an enum type solves this problem. When we write:

    Enum open_modes( input = 1, output, append );

    we define a new type open_modes. Valid values ​​for an object of this type are limited to the set of 1, 2, and 3, with each of the specified values ​​having a mnemonic name. We can use the name of this new type to define both the object of that type and the type of the function's formal parameters:

    Void open_file(string file_name, open_modes om);

    input, output and append are enumeration elements. The set of enumeration elements specifies the allowed set of values ​​for an object of a given type. A variable of type open_modes (in our example) is initialized with one of these values; it can also be assigned any of them. For example:

    Open_file("Phoenix and the Crane", append);

    An attempt to assign a value other than one of the enumeration elements to a variable of this type (or pass it as a parameter to a function) will cause a compilation error. Even if we try to pass an integer value corresponding to one of the enumeration elements, we will still receive an error:

    // error: 1 is not an element of the enumeration open_modes open_file("Jonah", 1);

    There is a way to define a variable of type open_modes, assign it the value of one of the enumeration elements and pass it as a parameter to the function:

    Open_modes om = input; // ... om = append; open_file("TailTell", om);

    However, it is not possible to obtain the names of such elements. If we write the output statement:

    Cout<< input << " " << om << endl;

    then we still get:

    This problem is solved by defining a string array in which the element with an index equal to the value of the enumeration element will contain its name. Given such an array, we can write:

    Cout<< open_modes_table[ input ] << " " << open_modes_table[ om ] << endl Будет выведено: input append

    Additionally, you cannot iterate over all the values ​​of an enumeration:

    // not supported for (open_modes iter = input; iter != append; ++inter) // ...

    To define an enumeration, use the enum keyword, and element names are specified in curly braces, separated by commas. By default, the first one is 0, the next one is 1, and so on. You can change this rule using the assignment operator. In this case, each subsequent element without an explicitly specified value will be 1 more than the element that comes before it in the list. In our example, we explicitly specified the value 1 for input, and output and append will be equal to 2 and 3. Here is another example:

    // shape == 0, sphere == 1, cylinder == 2, polygon == 3 enum Forms( share, spere, cylinder, polygon );

    Integer values ​​corresponding to different elements of the same enumeration do not have to be different. For example:

    // point2d == 2, point2w == 3, point3d == 3, point3w == 4 enum Points ( point2d=2, point2w, point3d=3, point3w=4 );

    An object whose type is an enumeration can be defined, used in expressions, and passed to a function as an argument. Such an object is initialized only with the value of one of the enumeration elements, and only that value is assigned to it, either explicitly or as the value of another object of the same type. Even integer values ​​corresponding to valid enumeration elements cannot be assigned to it:

    Void mumble() ( Points pt3d = point3d; // correct: pt2d == 3 // error: pt3w is initialized with type int Points pt3w = 3; // error: polygon is not included in the Points enumeration pt3w = polygon; // correct: both object of type Points pt3w = pt3d )

    However, in arithmetic expressions, an enumeration can be automatically converted to type int. For example:

    Const int array_size = 1024; // correct: pt2w is converted to int
    int chunk_size = array_size * pt2w;

    3.9. Type "array"

    We already touched on arrays in section 2.1. An array is a set of elements of the same type, accessed by an index - the serial number of the element in the array. For example:

    Int ival;

    defines ival as an int variable, and the instruction

    Int ia[ 10 ];

    specifies an array of ten objects of type int. To each of these objects, or array elements, can be accessed using the index operation:

    Ival = ia[ 2 ];

    assigns the variable ival the value of array element ia with index 2. Similarly

    Ia[ 7 ] = ival;

    assigns the element at index 7 the value ival.

    An array definition consists of a type specifier, an array name, and a size. The size specifies the number of array elements (at least 1) and is enclosed in square brackets. The size of the array must be known at the compilation stage, and therefore it must be a constant expression, although it is not necessarily specified as a literal. Here are examples of correct and incorrect array definitions:

    Extern int get_size(); // buf_size and max_files constants
    const int buf_size = 512, max_files = 20;
    int staff_size = 27; // correct: constant char input_buffer[ buf_size ]; // correct: constant expression: 20 - 3 char *fileTable[ max_files-3 ]; // error: not a constant double salaries[ staff_size ]; // error: not a constant expression int test_scores[ get_size() ];

    The buf_size and max_files objects are constants, so the input_buffer and fileTable array definitions are correct. But staff_size is a variable (albeit initialized with the constant 27), which means salaries are unacceptable. (The compiler is unable to find the value of the staff_size variable when the salaries array is defined.)
    The expression max_files-3 can be evaluated at compile time, so the fileTable array definition is syntactically correct.
    Element numbering starts at 0, so for an array of 10 elements the correct index range is not 1 – 10, but 0 – 9. Here is an example of iterating through all the elements of the array:

    Int main() ( const int array_size = 10; int ia[ array_size ]; for (int ix = 0; ix< array_size; ++ ix)
    ia[ ix ] = ix;
    }

    When defining an array, you can explicitly initialize it by listing the values ​​of its elements in curly braces, separated by commas:

    Const int array_size = 3; int ia[ array_size ] = ( 0, 1, 2 );

    If we explicitly specify a list of values, we don’t have to specify the size of the array: the compiler itself will count the number of elements:

    // array of size 3 int ia = ( 0, 1, 2 );

    When both the size and the list of values ​​are explicitly specified, three options are possible. If the size and number of values ​​coincide, everything is obvious. If the list of values ​​is shorter than the specified size, the remaining array elements are initialized to zero. If there are more values ​​in the list, the compiler displays an error message:

    // ia ==> ( 0, 1, 2, 0, 0 ) const int array_size = 5; int ia[ array_size ] = ( 0, 1, 2 );

    A character array can be initialized not only with a list of character values ​​in curly braces, but also with a string literal. However, there are some differences between these methods. Let's say

    Const char cal = ("C", "+", "+" ); const char cal2 = "C++";

    The dimension of the ca1 array is 3, the ca2 array is 4 (in string literals, the terminating null character is taken into account). The following definition will cause a compilation error:

    // error: the string "Daniel" consists of 7 elements const char ch3[ 6 ] = "Daniel";

    An array cannot be assigned the value of another array, and initialization of one array by another is not allowed. Additionally, it is not allowed to use an array of references. Here are examples of the correct and incorrect use of arrays:

    Const int array_size = 3; int ix, jx, kx; // correct: array of pointers of type int* int *iar = ( &ix, &jx, &kx ); // error: reference arrays are not allowed int &iar = ( ix, jx, kx ); int main()
    {
    int ia3( array_size ]; // correct
    // error: built-in arrays cannot be copied
    ia3 = ia;
    return 0;
    }

    To copy one array to another, you have to do this for each element separately:

    Const int array_size = 7; int ia1 = ( 0, 1, 2, 3, 4, 5, 6 ); int main() (
    int ia3[ array_size ]; for (int ix = 0; ix< array_size; ++ix)
    ia2[ ix ] = ia1[ ix ]; return 0;
    }

    An array index can be any expression that produces a result of an integer type. For example:

    Int someVal, get_index(); ia2[ get_index() ] = someVal;

    We emphasize that the C++ language does not provide control of array indices - neither at the compilation stage, nor at the runtime stage. The programmer himself must ensure that the index does not go beyond the boundaries of the array. Errors when working with an index are quite common. Unfortunately, it is not very difficult to find examples of programs that compile and even work, but nevertheless contain fatal errors that sooner or later lead to crash.

    Exercise 3.22

    Which of the following array definitions are incorrect? Explain.

    (a) int ia[ buf_size ]; (d) int ia[ 2 * 7 - 14 ] (b) int ia[ get_size() ]; (e) char st[ 11 ] = "fundamental"; (c) int ia[ 4 * 7 - 14 ];

    Exercise 3.23

    The following code snippet must initialize each element of the array with an index value. Find the mistakes made:

    Int main() ( const int array_size = 10; int ia[ array_size ]; for (int ix = 1; ix<= array_size; ++ix)
    ia[ ia ] = ix; // ...
    }

    3.9.1. Multidimensional arrays

    In C++, it is possible to use multidimensional arrays; when declaring them, you must indicate the right boundary of each dimension in separate square brackets. Here is the definition of a two-dimensional array:

    Int ia[ 4 ][ 3 ];

    The first value (4) specifies the number of rows, the second (3) – the number of columns. The ia object is defined as an array of four strings of three elements each. Multidimensional arrays can also be initialized:

    Int ia[ 4 ][ 3 ] = ( ( 0, 1, 2 ), ( 3, 4, 5 ), ( 6, 7, 8 ), ( 9, 10, 11 ) );

    Inner curly braces, which break the list of values ​​into lines, are optional and are generally used to make the code easier to read. The initialization below is exactly the same as the previous example, although it is less clear:

    Int ia = ( 0,1,2,3,4,5,6,7,8,9,10,11 );

    The following definition initializes only the first elements of each line. The remaining elements will be zero:

    Int ia[ 4 ][ 3 ] = ( (0), (3), (6), (9) );

    If you omit the inner curly braces, the result will be completely different. All three elements of the first row and the first element of the second will receive the specified value, and the rest will be implicitly initialized to 0.

    Int ia[ 4 ][ 3 ] = ( 0, 3, 6, 9 );

    When accessing elements of a multidimensional array, you must use indexes for each dimension (they are enclosed in square brackets). This is what initializing a two-dimensional array looks like using nested loops:

    Int main() ( const int rowSize = 4; const int colSize = 3; int ia[ rowSize ][ colSize ]; for (int = 0; i< rowSize; ++i)
    for (int j = 0; j< colSize; ++j)
    ia[ i ][ j ] = i + j j;
    }

    Design

    Ia[ 1, 2 ]

    is valid from the point of view of C++ syntax, but it does not mean at all what an inexperienced programmer expects. This is not a declaration of a two-dimensional 1-by-2 array. An aggregate in square brackets is a comma-separated list of expressions that will result in the final value 2 (see the comma operator in Section 4.2). Therefore the declaration ia is equivalent to ia. This is another opportunity to make a mistake.

    3.9.2. Relationship between arrays and pointers

    If we have an array definition:

    Int ia = ( 0, 1, 1, 2, 3, 5, 8, 13, 21 );

    then what does simply indicating his name in the program mean?

    Using an array identifier in a program is equivalent to specifying the address of its first element:

    Similarly, you can access the value of the first element of an array in two ways:

    // both expressions return the first element *ia; ia;

    To take the address of the second element of the array, we must write:

    As we mentioned earlier, the expression

    also gives the address of the second element of the array. Accordingly, its meaning is given to us in the following two ways:

    *(ia+1); ia;

    Note the difference in expressions:

    *ia+1 and *(ia+1);

    The dereference operation has a higher priority than the addition operation (operator priorities are discussed in Section 4.13). Therefore, the first expression first dereferences the variable ia and gets the first element of the array, and then adds 1 to it. The second expression delivers the value of the second element.

    You can traverse an array using an index, as we did in the previous section, or using pointers. For example:

    #include int main() ( int ia = ( 0, 1, 1, 2, 3, 5, 8, 13, 21 ); int *pbegin = ia; int *pend = ia + 9; while (pbegin != pend) ( cout<< *pbegin <<; ++pbegin; } }

    The pbegin pointer is initialized to the address of the first element of the array. Each pass through the loop increments this pointer by 1, which means it is shifted to the next element. How do you know where to stay? In our example, we defined a second pend pointer and initialized it with the address following the last element of the ia array. As soon as the value of pbegin becomes equal to pend, we know that the array has ended. Let's rewrite this program so that the beginning and end of the array are passed as parameters to a certain generalized function that can print an array of any size:

    #include void ia_print(int *pbegin, int *pend) (
    while (pbegin != pend) (
    cout<< *pbegin << " ";
    ++pbegin;
    }
    ) int main()
    {
    int ia = ( 0, 1, 1, 2, 3, 5, 8, 13, 21 );
    ia_print(ia, ia + 9);
    }

    Our function has become more universal, however, it can only work with arrays of type int. There is a way to remove this limitation: convert this function into a template (templates were briefly introduced in section 2.5):

    #include template void print(elemType *pbegin, elemType *pend) ( while (pbegin != pend) ( cout<< *pbegin << " "; ++pbegin; } }

    We can now call our print() function to print arrays of any type:

    Int main() ( int ia = ( 0, 1, 1, 2, 3, 5, 8, 13, 21 ); double da = ( 3.14, 6.28, 12.56, 25.12 ); string sa = ( "piglet", " eeyore", "pooh" ); print(ia, ia+9);
    print(da, da+4);
    print(sa, sa+3);
    }

    We wrote generalized function. The standard library provides a set of generic algorithms (we already mentioned this in Section 3.4) implemented in a similar way. The parameters of such functions are pointers to the beginning and end of the array with which they perform certain actions. Here, for example, is what calls to a generalized sorting algorithm look like:

    #include int main() ( int ia = ( 107, 28, 3, 47, 104, 76 ); string sa = ( "piglet", "eeyore", "pooh" ); sort(ia, ia+6);
    sort(sa, sa+3);
    };

    (We'll go into more detail about generalized algorithms in Chapter 12; the Appendix will give examples of their use.)
    The C++ Standard Library contains a set of classes that encapsulate the use of containers and pointers. (This was discussed in Section 2.8.) In the next section, we'll take a look at the standard container type vector, which is an object-oriented implementation of an array.

    3.10. Vector class

    Using the vector class (see Section 2.8) is an alternative to using built-in arrays. This class provides much more functionality, so its use is preferable. However, there are situations when you cannot do without arrays of the built-in type. One of these situations is the processing of command line parameters passed to the program, which we will discuss in section 7.8. The vector class, like the string class, is part of the C++ standard library.
    To use the vector you must include the header file:

    #include

    There are two completely different approaches to using a vector, let's call them the array idiom and the STL idiom. In the first case, a vector object is used in exactly the same way as an array of a built-in type. A vector of a given dimension is determined:

    Vector< int >ivec(10);

    which is similar to defining an array of a built-in type:

    Int ia[ 10 ];

    To access individual elements of a vector, the index operation is used:

    Void simp1e_examp1e() ( const int e1em_size = 10; vector< int >ivec(e1em_size); int ia[ e1em_size ]; for (int ix = 0; ix< e1em_size; ++ix)
    ia[ ix ] = ivec[ ix ]; // ...
    }

    We can find out the dimension of a vector using the size() function and check if the vector is empty using the empty() function. For example:

    Void print_vector(vector ivec) ( if (ivec.empty()) return; for (int ix=0; ix< ivec.size(); ++ix)
    cout<< ivec[ ix ] << " ";
    }

    The elements of the vector are initialized with default values. For numeric types and pointers, this value is 0. If the elements are class objects, then the initiator for them is specified by the default constructor (see section 2.3). However, the initiator can also be specified explicitly using the form:

    Vector< int >ivec(10, -1);

    All ten elements of the vector will be equal to -1.
    An array of a built-in type can be explicitly initialized with a list:

    Int ia[ 6 ] = ( -2, -1, O, 1, 2, 1024 );

    A similar action is not possible for an object of the vector class. However, such an object can be initialized using a built-in array type:

    // 6 ia elements are copied into ivec vector< int >ivec(ia, ia+6);

    The constructor of the vector ivec is passed two pointers - a pointer to the beginning of the array ia and to the element following the last one. It is permissible to specify not the entire array, but a certain range of it, as a list of initial values:

    // 3 elements are copied: ia, ia, ia vector< int >ivec(&ia[ 2 ], &ia[ 5 ]);

    Another difference between a vector and a built-in array is the ability to initialize one vector object with another and use the assignment operator to copy objects. For example:

    Vector< string >svec; void init_and_assign() ( // one vector is initialized by another vector< string >user_names(svec); // ... // one vector is copied to another
    svec = user_names;
    }

    When we talk about the STL idiom, we mean a completely different approach to using a vector. Instead of immediately specifying the desired size, we define an empty vector:

    Vector< string >text;

    Then we add elements to it using various functions. For example, the push_back() function inserts an element at the end of a vector. Here's a piece of code that reads a sequence of lines from standard input and adds them to a vector:

    String word; while (cin >> word) ( text.push_back(word); // ... )

    Although we can use the index operation to iterate over the elements of a vector:

    Cout<< "считаны слова: \n"; for (int ix =0; ix < text.size(); ++ix) cout << text[ ix ] << " "; cout << endl;

    It is more typical within this idiom to use iterators:

    Cout<< "считаны слова: \n"; for (vector::iterator it = text.begin(); it != text.end(); ++it) cout<< *it << " "; cout << endl;

    An iterator is a standard library class that is essentially a pointer to an array element.
    Expression

    dereferences the iterator and gives the vector element itself. Instructions

    Moves the pointer to the next element. There is no need to mix these two approaches. If you follow the STL idiom when defining an empty vector:

    Vector ivec;

    It would be a mistake to write:

    We don't have a single element of the ivec vector yet; The number of elements is determined using the size() function.

    The opposite mistake can also be made. If we defined a vector of some size, for example:

    Vector ia(10);

    Then inserting elements increases its size, adding new elements to existing ones. Although this seems obvious, a novice programmer might well write:

    Const int size = 7; int ia[ size ] = ( 0, 1, 1, 2, 3, 5, 8 ); vector< int >ivec(size); for (int ix = 0; ix< size; ++ix) ivec.push_back(ia[ ix ]);

    This meant initializing the ivec vector with the values ​​of the ia elements, which instead resulted in an ivec vector of size 14.
    Following the STL idiom, you can not only add but also remove elements from a vector. (We will look at all this in detail and with examples in Chapter 6.)

    Exercise 3.24

    Are there any errors in the following definitions?
    int ia[ 7 ] = ( 0, 1, 1, 2, 3, 5, 8 );

    (a)vector< vector< int >>ivec;
    (b)vector< int >ivec = ( 0, 1, 1, 2, 3, 5, 8 );
    (c)vector< int >ivec(ia, ia+7);
    (d)vector< string >svec = ivec;
    (e)vector< string >svec(10, string("null"));

    Exercise 3.25

    Implement the following function:
    bool is_equal(const int*ia, int ia_size,
    const vector &ivec);
    The is_equal() function compares two containers element by element. In the case of containers of different sizes, the “tail” of the longer one is not taken into account. It is clear that if all compared elements are equal, the function returns true, if at least one is different, false. Use an iterator to iterate over elements. Write a main() function that calls is_equal().

    3.11. class complex

    The class of complex numbers complex is another class from the standard library. As usual, to use it you need to include the header file:

    #include

    A complex number consists of two parts - real and imaginary. The imaginary part is the square root of a negative number. A complex number is usually written in the form

    where 2 is the real part, and 3i is the imaginary part. Here are examples of definitions of objects of type complex:

    // purely imaginary number: 0 + 7-i complex< double >purei(0, 7); // imaginary part is 0: 3 + Oi complex< float >rea1_num(3); // both real and imaginary parts are equal to 0: 0 + 0-i complex< long double >zero; // initialization of one complex number with another complex< double >purei2(purei);

    Since complex, like vector, is a template, we can instantiate it with the types float, double and long double, as in the examples given. You can also define an array of elements of type complex:

    Complex< double >conjugate[ 2 ] = ( complex< double >(2, 3), complex< double >(2, -3) };

    Complex< double >*ptr = complex< double >&ref = *ptr;

    3.12. typedef directive

    The typedef directive allows you to specify a synonym for a built-in or custom data type. For example:

    Typedef double wages; typedef vector vec_int; typedef vec_int test_scores; typedef bool in_attendance; typedef int *Pint;

    Names defined using the typedef directive can be used in exactly the same way as type specifiers:

    // double hourly, weekly; wages hourly, weekly; //vector vecl(10);
    vec_int vecl(10); //vector test0(c1ass_size); const int c1ass_size = 34; test_scores test0(c1ass_size); //vector< bool >attendance; vector< in_attendance >attendance(c1ass_size); // int *table[ 10 ]; Pint table [10];

    This directive begins with the typedef keyword, followed by a type specifier, and ends with an identifier, which becomes a synonym for the specified type.
    What are names defined using the typedef directive used for? By using mnemonic names for data types, you can make your program easier to understand. In addition, it is common to use such names for complex composite types that are otherwise difficult to understand (see the example in Section 3.14), to declare pointers to functions and member functions of a class (see Section 13.6).
    Below is an example of a question that almost everyone answers incorrectly. The error is caused by a misunderstanding of the typedef directive as a simple text macro substitution. Definition given:

    Typedef char *cstring;

    What is the type of the cstr variable in the following declaration:

    Extern const cstring cstr;

    The answer that seems obvious is:

    Const char *cstr

    However, this is not true. The const specifier refers to cstr, so the correct answer is a const pointer to char:

    Char *const cstr;

    3.13. volatile specifier

    An object is declared volatile if its value can be changed without the compiler noticing, such as a variable updated by the system clock. This specifier tells the compiler that it does not need to optimize the code to work with this object.
    The volatile specifier is used similarly to the const specifier:

    Volatile int disp1ay_register; volatile Task *curr_task; volatile int ixa[ max_size ]; volatile Screen bitmap_buf;

    display_register is a volatile object of type int. curr_task – pointer to a volatile object of the Task class. ixa is an unstable array of integers, and each element of such an array is considered unstable. bitmap_buf is a volatile object of the Screen class, each of its data members is also considered volatile.
    The only purpose of using the volatile specifier is to tell the compiler that it cannot determine who can change the value of a given object and how. Therefore, the compiler should not perform optimizations on code that uses this object.

    3.14. class pair

    The pair class of the C++ standard library allows us to define a pair of values ​​with one object if there is any semantic connection between them. These values ​​can be the same or different types. To use this class you must include the header file:

    #include

    For example, instructions

    Pair< string, string >author("James", "Joyce");

    creates an author object of type pair, consisting of two string values.
    The individual parts of a pair can be obtained using the first and second members:

    String firstBook; if (Joyce.first == "James" &&
    Joyce.second == "Joyce")
    firstBook = "Stephen Hero";

    If you need to define several objects of the same type of this class, it is convenient to use the typedef directive:

    Typedef pair< string, string >Authors; Authors proust("marcel", "proust"); Authors joyce("James", "Joyce"); Authors musil("robert", "musi1");

    Here is another example of using a pair. The first value contains the name of some object, the second is a pointer to the table element corresponding to this object.

    Class EntrySlot; extern EntrySlot* 1ook_up(string); typedef pair< string, EntrySlot* >SymbolEntry; SymbolEntry current_entry("author", 1ook_up("author"));
    // ... if (EntrySlot *it = 1ook_up("editor")) (
    current_entry.first = "editor";
    current_entry.second = it;
    }

    (We'll return to the pair class when we talk about container types in Chapter 6 and about generic algorithms in Chapter 12.)

    3.15. Class Types

    The class mechanism allows you to create new data types; with its help, the string, vector, complex and pair types discussed above were introduced. In Chapter 2, we introduced the concepts and mechanisms that support object and object-oriented approaches, using the Array class as an example. Here, based on the object approach, we will create a simple String class, the implementation of which will help us understand, in particular, operator overloading - we talked about it in section 2.3. (Classes are covered in detail in Chapters 13, 14, and 15.) We have given a brief description of the class in order to give more interesting examples. A reader new to C++ may want to skip this section and wait for a more systematic description of classes in later chapters.)
    Our String class must support initialization by an object of the String class, a string literal, and the built-in string type, as well as the operation of assigning values ​​to these types. We use class constructors and an overloaded assignment operator for this. Access to individual String characters will be implemented as an overloaded index operation. In addition, we will need: the size() function to obtain information about the length of the string; the operation of comparing objects of type String and a String object with a string of a built-in type; as well as the I/O operations of our object. Finally, we implement the ability to access the internal representation of our string as a built-in string type.
    A class definition begins with the keyword class, followed by an identifier—the name of the class, or type. In general, a class consists of sections preceded by the words public (open) and private (closed). A public section typically contains a set of operations supported by the class, called methods or member functions of the class. These member functions define the public interface of the class, in other words, the set of actions that can be performed on objects of that class. A private section typically includes data members that provide internal implementation. In our case, the internal members include _string - a pointer to char, as well as _size of type int. _size will store information about the length of the string, and _string will be a dynamically allocated array of characters. This is what a class definition looks like:

    #include class String; istream& operator>>(istream&, String&);
    ostream& operator<<(ostream&, const String&); class String {
    public:
    // set of constructors
    // for automatic initialization
    // String strl; // String()
    // String str2("literal"); // String(const char*);
    // String str3(str2); // String(const String&); String();
    String(const char*);
    String(const String&); // destructor
    ~String(); // assignment operators
    // strl = str2
    // str3 = "a string literal" String& operator=(const String&);
    String& operator=(const char*); // equality testing operators
    // strl == str2;
    // str3 == "a string literal"; bool operator==(const String&);
    bool operator==(const char*); // overloading the access operator by index
    // strl[ 0 ] = str2[ 0 ]; char& operator(int); // access to class members
    int size() ( return _size; )
    char* c_str() ( return _string; ) private:
    int_size;
    char *_string;
    }

    The String class has three constructors. As discussed in Section 2.3, the overloading mechanism allows you to define multiple implementations of functions with the same name, as long as they all differ in the number and/or types of their parameters. First constructor

    It is the default constructor because it does not require an explicit initial value. When we write:

    For str1, such a constructor is called.
    The two remaining constructors each have one parameter. Yes, for

    String str2("character string");

    The constructor is called

    String(const char*);

    String str3(str2);

    Constructor

    String(const String&);

    The type of constructor called is determined by the type of the actual argument. The last of the constructors, String(const String&), is called a copy constructor because it initializes an object with a copy of another object.
    If you write:

    String str4(1024);

    This will cause a compilation error, because there is no constructor with a parameter of type int.
    An overloaded operator declaration has the following format:

    Return_type operator op(parameter_list);

    Where operator is a keyword, and op is one of the predefined operators: +, =, ==, and so on. (See Chapter 15 for a precise definition of the syntax.) Here is the declaration of the overloaded index operator:

    Char& operator(int);

    This operator has a single parameter of type int and returns a reference to a char. An overloaded operator may itself be overloaded if the parameter lists of individual instantiations differ. For our String class, we will create two different assignment and equality operators.
    To call a member function, use the member access operators dot (.) or arrow (->). Let us have declarations of objects of type String:

    String object("Danny");
    String *ptr = new String("Anna");
    String array;
    Here's what a call to size() looks like on these objects:
    vector sizes(3);

    // member access for objects (.); // objects has a size of 5 sizes[ 0 ] = object.size(); // member access for pointers (->)
    // ptr has size 4
    sizes[ 1 ] = ptr->size(); // access member (.)
    // array has size 0
    sizes[ 2 ] = array.size();

    It returns 5, 4 and 0 respectively.
    Overloaded operators are applied to an object in the same way as regular ones:

    String name("Yadie"); String name2("Yodie"); // bool operator==(const String&)
    if (name == name2)
    return;
    else
    // String& operator=(const String&)
    name = name2;

    A member function declaration must be inside a class definition, and a function definition can be inside or outside a class definition. (Both the size() and c_str() functions are defined inside a class.) If a function is defined outside a class, then we must specify, among other things, which class it belongs to. In this case, the definition of the function is placed in the source file, say String.C, and the definition of the class itself is placed in the header file (String.h in our example), which must be included in the source:

    // contents of the source file: String.C // enabling the definition of the String class
    #include "String.h" // include the definition of the strcmp() function
    #include
    bool // return type
    String:: // class to which the function belongs
    operator== // function name: equality operator
    (const String &rhs) // list of parameters
    {
    if (_size != rhs._size)
    return false;
    return strcmp(_strinq, rhs._string) ?
    false: true;
    }

    Recall that strcmp() is a C standard library function. It compares two built-in strings, returning 0 if the strings are equal and non-zero if they are not equal. The conditional operator (?:) tests the value before the question mark. If true, the value of the expression to the left of the colon is returned; otherwise, the value to the right is returned. In our example, the value of the expression is false if strcmp() returned a non-zero value, and true if it returned a zero value. (The conditional operator is discussed in section 4.7.)
    The comparison operation is used quite often, the function that implements it turned out to be small, so it is useful to declare this function built-in (inline). The compiler substitutes the text of the function instead of calling it, so no time is wasted on such a call. (Built-in functions are discussed in Section 7.6.) A member function defined within a class is built-in by default. If it is defined outside the class, in order to declare it built-in, you need to use the inline keyword:

    Inline bool String::operator==(const String &rhs) ( // the same thing )

    The definition of a built-in function must be in the header file that contains the class definition. By redefining the == operator as an inline operator, we must move the function text itself from the String.C file to the String.h file.
    The following is the implementation of the operation of comparing a String object with a built-in string type:

    Inline bool String::operator==(const char *s) ( return strcmp(_string, s) ? false: true; )

    The constructor name is the same as the class name. It is considered not to return a value, so there is no need to specify a return value either in its definition or in its body. There can be several constructors. Like any other function, they can be declared inline.

    #include // default constructor inline String::String()
    {
    _size = 0;
    _string = 0;
    ) inline String::String(const char *str) ( if (! str) ( _size = 0; _string = 0; ) else ( _size = str1en(str); strcpy(_string, str); ) // copy constructor
    inline String::String(const String &rhs)
    {
    size = rhs._size;
    if (! rhs._string)
    _string = 0;
    else(
    _string = new char[ _size + 1 ];
    } }

    Since we dynamically allocated memory using the new operator, we need to free it by calling delete when we no longer need the String object. Another special member function serves this purpose - the destructor, which is automatically called on an object at the moment when this object ceases to exist. (See Chapter 7 about object lifetime.) The name of a destructor is formed from the tilde character (~) and the name of the class. Here is the definition of the String class destructor. This is where we call the delete operation to free the memory allocated in the constructor:

    Inline String: :~String() ( delete _string; )

    Both overloaded assignment operators use the special keyword this.
    When we write:

    String namel("orville"), name2("wilbur");
    name = "Orville Wright";
    this is a pointer that addresses the name1 object within the function body of the assignment operation.
    this always points to the class object through which the function is called. If
    ptr->size();
    obj[ 1024 ];

    Then inside size() the value of this will be the address stored in ptr. Inside the index operation, this contains the address of obj. By dereferencing this (using *this), we get the object itself. (The this pointer is described in detail in Section 13.4.)

    Inline String& String::operator=(const char *s) ( if (! s) ( _size = 0; delete _string; _string = 0; ) else ( _size = str1en(s); delete _string; _string = new char[ _size + 1 ]; strcpy(_string, s); return *this;

    When implementing an assignment operation, one mistake that is often made is that they forget to check whether the object being copied is the same one into which the copy is being made. We will perform this check using the same this pointer:

    Inline String& String::operator=(const String &rhs) ( // in the expression // namel = *pointer_to_string // this represents name1, // rhs - *pointer_to_string. if (this != &rhs) (

    Here is the complete text of the operation of assigning an object of the same type to a String object:

    Inline String& String::operator=(const String &rhs) ( if (this != &rhs) ( delete _string; _size = rhs._size; if (! rhs._string)
    _string = 0;
    else(
    _string = new char[ _size + 1 ];
    strcpy(_string, rhs._string);
    }
    }
    return *this;
    }

    The operation of taking an index is almost identical to its implementation for the Array array that we created in section 2.3:

    #include inline char&
    String::operator (int elem)
    {
    assert(elem >= 0 && elem< _size);
    return _string[ elem ];
    }

    Input and output operators are implemented as separate functions rather than class members. (We'll talk about the reasons for this in Section 15.2. Sections 20.4 and 20.5 talk about overloading the iostream library's input and output operators.) Our input operator can read a maximum of 4095 characters. setw() is a predefined manipulator, it reads a given number of characters minus 1 from the input stream, thereby ensuring that we do not overflow our internal buffer inBuf. (Chapter 20 discusses the setw() manipulator in detail.) To use manipulators, you must include the corresponding header file:

    #include inline istream& operator>>(istream &io, String &s) ( // artificial limit: 4096 characters const int 1imit_string_size = 4096; char inBuf[ limit_string_size ]; // setw() is included in the iostream library // it limits the size of the readable block to 1imit_string_size -l io >> setw(1imit_string_size) >> inBuf; s = mBuf; // String::operator=(const char*);

    The output operator needs access to the internal representation of the String. Since operator<< не является функцией-членом, он не имеет доступа к закрытому члену данных _string. Ситуацию можно разрешить двумя способами: объявить operator<< дружественным классу String, используя ключевое слово friend (дружественные отношения рассматриваются в разделе 15.2), или реализовать встраиваемую (inline) функцию для доступа к этому члену. В нашем случае уже есть такая функция: c_str() обеспечивает доступ к внутреннему представлению строки. Воспользуемся ею при реализации операции вывода:

    Inline ostream& operator<<(ostream& os, const String &s) { return os << s.c_str(); }

    Below is an example program using the String class. This program takes words from the input stream and counts their total number, as well as the number of "the" and "it" words, and records the vowels encountered.

    #include #include "String.h" int main() ( int aCnt = 0, eCnt = 0, iCnt = 0, oCnt = 0, uCnt = 0, theCnt = 0, itCnt = 0, wdCnt = 0, notVowel = 0; / / The words "The" and "It"
    // we will check using operator==(const char*)
    String but, the("the"), it("it"); // operator>>(ostream&, String&)
    while (cin >> buf) (
    ++wdCnt; // operator<<(ostream&, const String&)
    cout<< buf << " "; if (wdCnt % 12 == 0)
    cout<< endl; // String::operator==(const String&) and
    // String::operator==(const char*);
    if (buf == the | | buf == "The")
    ++theCnt;
    else
    if (buf == it || buf == "It")
    ++itCnt; // invokes String::s-ize()
    for (int ix =0; ix< buf.sizeO; ++ix)
    {
    // invokes String::operator(int)
    switch(buf[ ix ])
    {
    case "a": case "A": ++aCnt; break;
    case "e": case "E": ++eCnt; break;
    case "i": case "I": ++iCnt; break;
    case "o": case "0": ++oCnt; break;
    case "u": case "U": ++uCnt; break;
    default: ++notVowe1; break;
    }
    }
    ) // operator<<(ostream&, const String&)
    cout<< "\n\n"
    << "Слов: " << wdCnt << "\n\n"
    << "the/The: " << theCnt << "\n"
    << "it/It: " << itCnt << "\n\n"
    << "согласных: " < << "a: " << aCnt << "\n"
    << "e: " << eCnt << "\n"
    << "i: " << ICnt << "\n"
    << "o: " << oCnt << "\n"
    << "u: " << uCnt << endl;
    }

    Let's test the program: we will offer it a paragraph from a children's story written by one of the authors of this book (we will meet with this story in Chapter 6). Here is the result of the program:

    Alice Emma has long flowing red hair. Her Daddy says when the wind blows through her hair, it looks almost alive, 1ike a fiery bird in flight. A beautiful fiery bird, he tells her, magical but untamed. "Daddy, shush, there is no such thing," she tells him, at the same time wanting him to tell her more. Shyly, she asks, "I mean, Daddy, is there?" Words: 65
    the/The: 2
    it/It: 1
    consonants: 190
    a: 22
    e: 30
    i: 24
    o: 10
    u: 7

    Exercise 3.26

    Our implementations of constructors and assignment operators contain a lot of repetition. Consider moving duplicate code into a separate private member function, as was done in Section 2.3. Make sure the new option works.

    Exercise 3.27

    Modify the test program so that it also counts the consonants b, d, f, s, t.

    Exercise 3.28

    Write a member function that counts the number of occurrences of a character in a String using the following declaration:

    Class String ( public: // ... int count(char ch) const; // ... );

    Exercise 3.29

    Implement the string concatenation operator (+) so that it concatenates two strings and returns the result in a new String object. Here is the function declaration:

    Class String ( public: // ... String operator+(const String &rhs) const; // ... );

    Last update: 09/17/2017

    Each variable has a specific type. And this type determines what values ​​a variable can have, what operations can be performed on it, and how many bytes in memory it will occupy. The following basic data types are defined in the C++ language:

      bool : boolean type. Can take one of two values: true and false. The memory footprint for this type is not precisely defined.

      char : Represents a single ASCII character. Occupies 1 byte (8 bits) in memory. Can store any value from -128 to 127, or from 0 to 255

      signed char : Represents a single character. Occupies 1 byte (8 bits) in memory. Can store any value from -128 to 127

      unsigned char : Represents a single character. Occupies 1 byte (8 bits) in memory. Can store any value from 0 to 255

      wchar_t : Represents a wide character. On Windows it takes up 2 bytes (16 bits) of memory, on Linux it takes 4 bytes (32 bits). Can store any value from the range from 0 to 65,535 (for 2 bytes), or from 0 to 4,294,967,295 (for 4 bytes)

      char16_t: Represents a single Unicode character. Occupies 2 bytes (16 bits) in memory. Can store any value from 0 to 65,535

      char32_t : Represents a single Unicode character. Occupies 4 bytes (32 bits) in memory. Can store any value from 0 to 4,294,967,295

      short : Represents an integer in the range –32768 to 32767. Occupies 2 bytes (16 bits) of memory.

      This type also has synonyms short int, signed short int, signed short.

      unsigned short: Represents an integer in the range 0 to 65535. Occupies 2 bytes (16 bits) of memory.

      This type also has a synonym unsigned short int .

      int: represents an integer. Depending on the processor architecture, it can occupy 2 bytes (16 bits) or 4 bytes (32 bits). The range of limit values ​​accordingly can also vary from –32768 to 32767 (with 2 bytes) or from −2,147,483,648 to 2,147,483,647 (with 4 bytes). But in any case, the size must be greater than or equal to the size of the short type and less than or equal to the size of the long type

      This type has synonyms signed int and signed .

      unsigned int : Represents a positive integer. Depending on the processor architecture, it may occupy 2 bytes (16 bits) or 4 bytes (32 bits), and because of this, the range of limit values ​​may vary: from 0 to 65535 (for 2 bytes), or from 0 to 4,294,967,295 (for 4 bytes).

      unsigned can be used as a synonym for this type

      long : Represents an integer in the range −2,147,483,648 to 2,147,483,647. Occupies 4 bytes (32 bits) of memory.

      This type also has synonyms long int , signed long int and signed long

      unsigned long: Represents an integer in the range 0 to 4,294,967,295. Occupies 4 bytes (32 bits) of memory.

      Has the synonym unsigned long int .

      long long : Represents an integer in the range −9,223,372,036,854,775,808 to +9,223,372,036,854,775,807. Occupies typically 8 bytes (64 bits) of memory.

      Has synonyms long long int , signed long long int and signed long long .

      unsigned long long : Represents an integer in the range 0 to 18,446,744,073,709,551,615. Typically 8 bytes (64 bits) in memory.

      Has the synonym unsigned long long int .

      float : Represents a single-precision floating-point real number in the range +/- 3.4E-38 to 3.4E+38. Occupies 4 bytes (32 bits) in memory

      double : Represents a double precision floating point real number in the range +/- 1.7E-308 to 1.7E+308. Occupies 8 bytes (64 bits) in memory

      long double : Represents a double-precision floating-point real number of at least 8 bytes (64 bits). Depending on the size of the occupied memory, the range of valid values ​​may vary.

      void : type without value

    Thus, all data types except void can be divided into three groups: character (char, wchar_t, char16_t, char32_t), integer (short, int, long, long long) and floating point number types (float, double, long double).

    Character types

    The types used to represent characters in the application are char, wchar_t, char16_t, and char32_t.

    Let's define several variables:

    Char c="d"; wchar_t d="c";

    A char variable takes as its value one character in single quotes: char c ="d" . You can also assign a number from the range specified above in the list: char c = 120 . In this case, the value of the variable c will be the character that has code 120 in the ASCII character table.

    It is worth considering that to output wchar_t characters to the console, you should use not std::cout, but the std::wcout stream:

    #include int main() ( char a = "H"; wchar_t b = "e"; std::wcout<< a << b << "\n"; return 0; }

    In this case, the std::wcout stream can work with both char and wchar_t. And the std::cout stream for the wchar_t variable will output its numeric code instead of a character.

    The C++11 standard added the char16_t and char32_t types, which are oriented towards using Unicode. However, threads for working with these types have not yet been implemented at the OS level. Therefore, if you need to display the values ​​of variables of these types to the console, you need to convert the variables to types char or wchar_t:

    #include int main() ( char a = "H"; wchar_t b = "e"; char16_t c = "l"; char32_t d = "o"; std::cout<< a << (char)b << (char)c << (char)d << "\n"; return 0; }

    In this case, when outputting, the variables are preceded by a cast operation to the char type - (char) , due to which the values ​​of the variables b, c and d are converted to the char type and can be output to the console using the std::cout stream.

    Integer types

    Integer types are represented by the following types: short, unsigned short, int, unsigned int, long, unsigned long, long long and unsigned long long:

    Short a = -10; unsigned short b= 10; int c = -30; unsigned int d = 60; long e = -170; unsigned long f = 45; long long g = 89;

    Types of floating point numbers

    Floating-point and fractional number types are represented by float, double and long double:

    Float a = -10.45; double b = 0.00105; long double c = 30.890045;

    Data type sizes

    The list above shows for each type the size it occupies in memory. However, it is worth noting that compiler developers can choose the size limits for types independently, based on the hardware capabilities of the computer. The standard sets only the minimum values ​​that should be. For example, for the int and short types the minimum value is 16 bits, for the long type - 32 bits, for the long double type. In this case, the size of the long type must be no less than the size of the int type, and the size of the int type must not be less than the size of the short type, and the size of the long double type must be greater than double. For example, the g++ compiler for Windows uses 12 bytes for long doubles, and the compiler built into Visual Studio and also runs under Windows uses 8 bytes for long doubles. That is, even within the same platform, different compilers may approach the sizes of certain data types differently. But in general, the sizes that are indicated above when describing data types are used.

    However, there are situations when it is necessary to know exactly the size of a certain type. And for this, C++ has the sizeof() operator, which returns the size of the memory in bytes that the variable occupies:

    #include int main() ( long double number = 2; std::cout<< "sizeof(number) =" << sizeof(number); return 0; }

    Console output when compiling in g++:

    sizeof(number) = 12

    At the same time, when defining variables, it is important to understand that the value of a variable should not go beyond the limits outlined for its type. For example:

    Unsigned short number = -65535;

    The G++ compiler, when compiling a program with this line, will generate an error stating that the value -65535 is not within the range of valid values ​​for the unsigned short type and will be truncated.

    In Visual Studio, compilation can proceed without errors, but the number variable will receive the value 2 - the result of converting the number -65535 to an unsigned short type. That is, again, the result will not be exactly what is expected. The value of a variable is just a collection of bits in memory that are interpreted according to a specific type. And for different types, the same set of bits can be interpreted differently. Therefore, it is important to consider the value ranges for a given type when assigning a value to a variable.

    auto specifier

    Sometimes it can be difficult to determine the type of expression. And according to the latest standards, you can let the compiler infer the type of the object itself. And the auto specifier is used for this. Moreover, if we define a variable with the auto specifier, this variable must be initialized with some value:

    Auto number = 5;

    Based on the assigned value, the compiler will infer the type of the variable. Uninitialized variables with the auto qualifier are not allowed.

    This cheat sheet provides information about the main data types of the C++ programming language and the features of their implementation. Also, at the end of the record there is a table with the ranges of values ​​of these types.

    Data Type Concept

    The main purpose of any program is to process data. Different types of data are stored and processed differently. In any algorithmic language, every constant, variable, expression, or function must have a specific type.

    The data type defines:

    • internal representation of data in computer memory;
    • the set of values ​​that quantities of this type can take;
    • operations and functions that can be applied to the quantities of this type.

    Based on these characteristics, the programmer selects the type of each quantity used in the program to represent real objects. A required type declaration allows the compiler to check the validity of various program constructs. The type of value determines the machine instructions that will be used to process the data.

    All types of C++ language can be divided into basic And composite . The C++ language defines six main data types to represent integer, real, character and logical values. Based on these types, the programmer can enter a description composite types. These include arrays, enumerations, functions, structures, references, pointers, unions, and classes.

    Basic data types in C++

    Basic (standard) data types are often called arithmetic because they can be used in arithmetic operations. To describe the main types, the following are defined:

    1. int(int);
    2. char(char);
    3. wchar_t(widechar);
    4. bool(boolean);
    5. float(real);
    6. double (double precision real).

    The first four types are called integer ( whole ), the last two - floating point types . The code that the compiler generates for handling integer values ​​is different from the code for floating-point values.

    There are four type specifier , clarifying the internal representation and range of values ​​of standard types:

    • short (short);
    • long(long);
    • signed(signed);
    • unsigned.

    Integer type (int)

    The size of the int type is not defined by the standard, but depends on the computer and the compiler. For a 16-bit processor, 2 bytes are allocated for values ​​of this type, for a 32-bit processor - 4 bytes.

    The short specifier before the type name indicates to the compiler that 2 bytes must be allocated for the number, regardless of the processor bit capacity. The long specifier means that the integer value will occupy 4 bytes. So on a 16-bit computer the equivalents are int and short int, and on a 32-bit computer the equivalents are int and long int.

    Internal representation values ​​of integer type - an integer in binary code. When using the signed specifier, the most significant bit of the number is interpreted as signed (0 is a positive number, 1 is a negative number). The unsigned specifier allows only positive numbers to be represented, since the most significant bit is treated as part of the number's code. Thus, the range of values ​​of type int depends on the specifiers. The ranges of values ​​of integer type values ​​with various specifiers for IBM PC-compatible computers are given in the table “Value ranges of simple data types” at the end of the entry.

    By default, all integer types are considered signed, meaning the signed specifier can be omitted.

    Constants found in a program are assigned one type or another in accordance with their type. If for some reason the programmer is not satisfied with this type, he can explicitly indicate the required type using the suffixes L, l (long) and U, u (unsigned). For example, the constant 32L will be of type long and occupy 4 bytes. You can use the L and U suffixes at the same time, for example, 0x22UL or 05Lu.

    Note

    The types short int, long int, signed int, and unsigned int can be abbreviated to short, long, signed, and unsigned, respectively.

    Character type (char)

    The value of a character type is allocated a number of bytes sufficient to accommodate any character from the character set for a given computer, which is what determines the name of the type. Typically this is 1 byte. The char type, like other integer types, can be signed or unsigned. Signed values ​​can store values ​​in the range -128 to 127. Using the unsigned specifier, values ​​can range from 0 to 255. This is sufficient to store any character in the 256-character ASCII character set. Values ​​of the char type are also used to store integers that do not exceed the boundaries of the specified ranges.

    Extended character type (wchar_t)

    The wchar_t type is designed to work with a set of characters for which 1 byte is not enough to encode, for example, Unicode. The size of this type is implementation dependent; as a rule, it corresponds to the type short. String constants of type wchar_t are written with the prefix L, for example, L»Gates».

    Boolean type (bool)

    Boolean values ​​can only take the values ​​true and false, which are reserved words. The internal form of representing the value false is 0 (zero). Any other value is interpreted as true. When converted to an integer type, true has the value 1.

    Floating point types (float, double and long double)

    The C++ standard defines three data types for storing real values: float, double and long double.

    Floating-point data types are stored differently in computer memory than integer data types. The internal representation of a real number consists of two parts - the mantissa and the exponent. On IBM PC-compatible computers, float values ​​occupy 4 bytes, of which one binary digit is allocated for the mantissa sign, 8 bits for the exponent and 23 for the mantissa. The mantissa is a number greater than 1.0 but less than 2.0. Since the leading digit of the mantissa is always 1, it is not stored.

    For double values ​​that occupy 8 bytes, 11 and 52 bits are allocated for the exponent and mantissa, respectively. The length of the mantissa determines the precision of the number, and the length of the exponent determines its range. As you can see from the table at the end of the entry, with the same number of bytes allocated for float and long int values, the ranges of their permissible values ​​differ greatly due to internal representation form.

    The long specifier before a double type name indicates that 10 bytes are allocated for its value.

    Floating-point constants are of double type by default. You can explicitly specify the type of a constant using the suffixes F, f (float) and L, l (long). For example, the constant 2E+6L will be of type long double, and the constant 1.82f will be of type float.

    To write programs that are portable across platforms, you can't make assumptions about the size of the int type. To obtain it, you must use the sizeof operation, the result of which is the size of the type in bytes. For example, for the MS-DOS operating system sizeof (int) will result in 2, but for Windows 98 or OS/2 the result will be 4.

    The ANSI standard does not specify value ranges for basic types; only the relationships between their sizes are defined, for example:

    sizeof(float) ≤ slzeof(double) ≤ sizeof(long double)
    sizeof(char) ≤ slzeof(short) ≤ sizeof(int) ≤ sizeof(long)

    Note

    The minimum and maximum allowed values ​​for integer types are implementation dependent and are given in the header file (), characteristics of real types - in the file (), as well as in the numeric_limits class template

    type void

    In addition to those listed, the main types of the language include the void type, but the set of values ​​of this type is empty. It is used to define functions that do not return a value, to specify an empty list of function arguments, as the base type for pointers, and in type casting operations.

    Value ranges of simple data types in C++ for IBM PC-compatible computers

    Q: What does the term IBM PC-compatible computer mean?
    A: An IBM PC compatible computer is a computer that is architecturally close to the IBM PC, XT and AT. IBM PC-compatible computers are built on microprocessors compatible with the Intel 8086 (and, as you know, all later Intel processors are fully backward compatible with the 8086). In fact, these are almost all modern computers.

    Various types of integer and real types, differing in the range and accuracy of data representation, were introduced in order to give the programmer the opportunity to most effectively use the capabilities of specific equipment, since the speed of calculations and the amount of memory depend on the choice of type. But a program optimized for one type of computer may not be portable to other platforms, so in general you should avoid relying on specific characteristics of data types.

    Type Range of values Size (byte)
    bool true and false 1
    signed char -128 … 127 1
    unsigned char 0 … 255 1
    signed short int -32 768 … 32 767 2
    unsigned short int 0 … 65 535 2
    signed long int -2 147 483 648 … 2 147 483 647 4
    unsigned long int 0 … 4 294 967 295 4
    float 3.4e-38 … 3.4e+38 4
    double 1.7e-308 … 1.7C+308 8
    long double 3.4e-4932 … 3.4e+4932 10

    For real types, the table shows the absolute values ​​of the minimum and maximum values.

    Tags: C++ data types, auto, decltype, automatic type inference

    Data Types

    As in C, variables in C++ must have a valid name. That is, consist of numbers, letters and an underscore, should not begin with a number and should not coincide with function words, of which there are now more

    alignas alignof and and_eq
    asm auto bitand bitor
    bool break case catch
    char char16_t char32_t class
    complconstconstexprconst_cast
    continuedecltypedefaultdelete
    do double dynamic_cast else
    enumexplicitexportextern
    falsefloatforfriend
    gotoifinlineint
    longmutablenamespacenew
    noexcept not not_eq nullptr
    operatororor_eqprivate
    protectedpublicregisterreinterpret_cast
    returnshortsignedsizeof
    staticstatic_assertstatic_caststruct
    switchtemplatethisthread_local
    throwtruetrytypedef
    typeidtypenameunionunsigned
    usingvirtualvoidvolatile
    wchar_twhilexorxor_eq

    Like C, C++ is a case-sensitive language.

    Basic Data Types

    Basic data types in C++ can be divided into several groups

    Sign type. Signed type variables can be used to store a single character. The simplest type is char, which is 1 byte in size. There are also types to represent characters larger than one byte

    Actually, these types exist in C as well; we did not dwell in detail on studying the representation of strings.

    Integer data types. As in C, they can have signed and unsigned modifiers. As in C, the main types are char, int, long and long long. Nothing new has appeared here.

    Floating point numbers. Represented by float, double and long double types. Nothing new compared to C.

    All types described above are also called arithmetic. In addition to them, there is also an empty type - void (also nothing new compared to C) and a null pointer. Now, instead of NULL with its amazing properties, there is a new fundamental type nullptr_t with a single value nullptr, which stores a null pointer and is equal only to itself. At the same time, it can be cast to a null pointer of the desired type.

    The Boolean type was introduced into C++. It stores only two possible values, true and false.

    C++ also supports many composite data types, which will be discussed later.

    Declaring and initializing variables

    In C++, variables can be declared anywhere inside a function, not just at the very beginning of a block of code. Variables can also be declared inside a for loop.

    Float a; float b; float sum; float step; a = 3.0f; b = 4.3f; sum = 0.0f; step = 0.05f; for (float i = a; i< b; i += step) { sum += i * i; } float mid = sum / (b - a) / step;

    You can initialize variables during creation as in C

    Int x = 0;

    or, using the constructor

    Int x(0); double d(3.2);

    In addition, in C++ 2011 the so-called uniform initialization, a universal initialization that allows you to use one syntax to initialize any objects

    Struct Point ( int x; int y; ); struct Point position = ( 3, 4 ); Point *pt = new Point(6, 8); int length(5);

    Type inference

    In C++ 2011, the service word auto is used to automatically determine the type of variables. Often the type of a variable can be determined based on the right side of the initialization. In the case where the compiler can unambiguously determine the type, it can be specified using the auto function word:

    Auto x = 3; //equivalent to int x = 3; auto point = new Point; //equivalent to Point *point = new Point

    In addition, it is possible to set the type of a variable based on an existing type, using the service word decltype

    Int intX = 42; decltype(intX) intY = 33; //equivalent to int intY = 33; auto pt1 = new Point; decltype(pt1) p2 = new Point(2, 6); //equivalent to //Point *pt1 = new Point; //Point *pt2 = new Point(2, 6)

    Strings

    C++ does not have a base type string. However, there is a standard library called string, which provides a class for working with strings.

    #include #include void main() ( std::string first_name = "Vasya"; std::string last_name = ( "Pupkin" ); //string concatenation auto full_name = first_name + " " + last_name; std::string *department = new std ::string("Department of copying and scanning");<< full_name << std::endl; //сравнение строк std::string a = "A"; std::string b = "B"; if (first_name.compare(last_name) >0) ( std::cout<< a + " >" + b<< std::endl; } else { std::cout << a + " < " + b << std::endl; } //подстрока std::string subs = department->substr(0, 10); std::cout<< subs << std::endl; //замена подстроки std::cout << last_name.replace(0, 1, "G") << std::endl; //вставка std::string new_department = department->insert(department->length(), " and shreddering"); std::cout<< new_department << std::endl; delete department; system("pause"); }

    We'll get to know the standard string library in more detail later.

    In the C language, there is a distinction between the concepts of “data type” and “type modifier”. The data type is integer and the modifier is signed or unsigned. A signed integer will have both positive and negative values, while an unsigned integer will have only positive values. There are five basic types in the C language.

    • char – character.
    • A variable of type char has a size of 1 byte, its values ​​are various characters from the code table, for example: 'f', ':', 'j' (when written in the program, they are enclosed in single quotes).

    • int – whole.
    • The size of a variable of type int is not defined in the C language standard. In most programming systems, the size of an int variable corresponds to the size of an entire machine word. For example, in compilers for 16-bit processors, an int variable has a size of 2 bytes. In this case, the signed values ​​of this variable can lie in the range from -32768 to 32767.

    • float – real.
    • The float keyword allows you to define variables of real type. Their values ​​have a fractional part separated by a dot, for example: -5.6, 31.28, etc. Real numbers can also be written in floating point form, for example: -1.09e+4. The number before the symbol “e” is called the mantissa, and after the “e” is called the exponent. A variable of type float occupies 32 bits in memory. It can take values ​​in the range from 3.4e-38 to 3.4e+38.

    • double – double precision real;
    • The double keyword allows you to define a double precision real variable. It takes up twice as much memory space as a float variable. A double type variable can take values ​​in the range from 1.7e-308 to 1.7e+308.

    • void – no value.
    • The void keyword is used to neutralize the value of an object, for example, to declare a function that does not return any value.

    Variable types:

    Programs operate with various data, which can be simple or structured. Simple data are integer and real numbers, symbols and pointers (addresses of objects in memory). Integers do not have a fractional part, but real numbers do. Structured data is arrays and structures; they will be discussed below.

    A variable is a cell in computer memory that has a name and stores some value. The value of a variable can change during program execution. When a new value is written to a cell, the old one is erased.

    It is good style to name variables meaningfully. The variable name can contain from one to 32 characters. It is allowed to use lowercase and uppercase letters, numbers and the underscore, which is considered a letter in C. The first character must be a letter. The variable name cannot match reserved words.

    Type char

    char is the most economical type. The char type can be signed or unsigned. Denoted as “signed char” (signed type) and “unsigned char” (unsigned type). The signed type can store values ​​in the range -128 to +127. Unsigned – from 0 to 255. 1 byte of memory (8 bits) is allocated for a char variable.

    The signed and unsigned keywords indicate how the zero bit of the declared variable is interpreted, i.e., if the unsigned keyword is specified, then the zero bit is interpreted as part of a number, otherwise the zero bit is interpreted as signed.

    Type int

    The integer value int can be short or long. The short keyword is placed after the signed or unsigned keywords. So there are types: signed short int, unsigned short int, signed long int, unsigned long int.

    A variable of type signed short int (signed short integer) can take values ​​from -32768 to +32767, unsigned short int (unsigned short integer) - from 0 to 65535. Each of them is allocated exactly two bytes of memory (16 bits).

    When declaring a variable of type signed short int, the signed and short keywords may be omitted, and such variable type may be declared simply int. It is also possible to declare this type with one keyword, short.

    An unsigned short int variable can be declared as an unsigned int or an unsigned short.

    For each signed long int or unsigned long int value, 4 bytes of memory (32 bits) are allocated. The values ​​of variables of this type can be in the ranges from -2147483648 to 2147483647 and from 0 to 4294967295, respectively.

    There are also variables of the long long int type, for which 8 bytes of memory are allocated (64 bits). They can be signed or unsigned. For a signed type, the range of values ​​is from -9223372036854775808 to 9223372036854775807, for an unsigned type - from 0 to 18446744073709551615. A signed type can also be declared simply by two long long keywords.

    Type Range Hex range Size
    unsigned char 0 … 255 0x00...0xFF 8 bit
    signed char
    or just
    char
    -128 … 127 -0x80…0x7F 8 bit
    unsigned short int
    or just
    unsigned int or unsigned short
    0 … 65535 0x0000…0xFFFF 16 bit
    signed short int or signed int
    or just
    short or int
    -32768 … 32767 0x8000…0x7FFF 16 bit
    unsigned long int
    or just
    unsigned long
    0 … 4294967295 0x00000000 … 0xFFFFFFFF 32 bit
    signed long
    or just
    long
    -2147483648 … 2147483647 0x80000000 … 0x7FFFFFFF 32 bit
    unsigned long long 0 … 18446744073709551615 0x0000000000000000 … 0xFFFFFFFFFFFFFFFF 64 bit
    signed long long
    or just
    long long
    -9223372036854775808 … 9223372036854775807 0x8000000000000000 … 0x7FFFFFFFFFFFFFF 64 bit

    Declaring Variables

    Variables are declared in a declaration statement. A declaration statement consists of a type specification and a comma-separated list of variable names. There must be a semicolon at the end.

    [modifiers] type_specifier identifier [, identifier] ...

    Modifiers – keywords signed, unsigned, short, long.
    A type specifier is a char or int keyword that specifies the type of the variable being declared.
    Identifier is the name of the variable.

    Char x; int a, b, c; unsigned long long y;

    When declared, a variable can be initialized, that is, assigned an initial value.

    Int x = 100;

    When declared, variable x will immediately contain the number 100. It is better to declare initialized variables on separate lines.