C++学习系列四:Arrays||Character sequences||Poiters||Dynamic Memory||Data Structures||Other data types

  • Arrays

    An array is a series of elements of the same type placed in contiguous memory locations that can be individually referenced by adding an index to a unique identifier.

    That means that, for example, five values of type int can be declared as an array without having to delacre 5 different variables (each with its own identifier). Instead, using an arraym the five int values are stored in contiguous memory locations, and all five can be accessed using the same identifier with the proper index.

    For example, an array containing 5 integer values of type int called foo could be represented as :

    When each blank panel represents an element of the array. In this case, these are values of type int.

    Like a regular variable, an array must be declared before it is used. A typical declaration for an array in C++ is :

    type name [elements];

    where type is a valid type (such as int, float, …), name is a valid identifier and the elements filed (which is always enclosed in square brackets []), specifies the length of the arry in terms of the number of elements.

    Therefore, the foo array, with five elements of type int, can be declared as :

    int foo [5];

    • Arrays as parameters

      At some point, we need to pass an array to a function as a parameter.

      In C++, it is not possible to pass the entire block of memory represented by an array to a function directly as an argument. But what can be passed instead is its address.

      In practice, this has almost the same effect, and it is a much faster and more efficient operation.

      To accept an array as parameter for a function, the parameters can be declared as the array type, but with empty brackets, omitting the actual size of the array.

      void procedure (int arg[]) 
      void procedure (int myarray[][3][4])
      

      In a way, passing an array as argument always loses a dimension.

      The reason behind is that, for historical reasons, arrays cannot be directly copied, and thus what is really passed is a pointer. This is a common souce of errors for novice programmers.

    • Library arrays

      The arrays explained above are directly implemented as a language feature, inherited from the C language. They are a great feature, but by restricting its copy and easily decay into pointers, they probably suffer from an excess of optimiztion.

      To overcome some of these issues with language ***built-in arrays***, C++ provides an alternative array type as a standard ***container***. It is a type template (a class template, in fact) defined in header .

      Suffice it to say that they operate in a similar way to built-in arrays, except that they allow being copied (an actually expensive that copies the entire block of memory, and thus to use with care) and decay into pointers only when explicitly told to do so (by means of its member data).

    • Initializing arrays

      By default, regular arrays of local scope (for example, those declared within a function) are left uninitialized. This means that none of its elements are set to any particular value; their contents are undermined at the point the array is declared.

      But the elements in an array can be explicitly initialized to specific values when it is declared, by enclosing those initial values in braces {}. For example:

      int foo[5] = {16, 2, 77, 40, 12071};

      This statement declares an array that can be represented like this:

      在这里插入图片描述

      The number of values between braces {} shall not be greater than the number of elements in the array. If declared with less, the remaining elements are set to their default values (which for fundamental types, means they are filled with zeroes).

      When an initialization of values is provided for an array, C++ allows the possibility of leaving the square brackets empty []. In this case, the compiler will assume automatically a size foe the array that matchs the number of values included between the braces {}.

      int foo[] = {16, 2, 3};
      

      Finally, the evolution of C++ has led to the adoption of universal initialization also for arrays. Therefore, there is no longer need for the equal sign between the declaration and the initializer.

      int foo[] = {10, 20, 30};
      int foo[] {10, 20, 30}
      

      Static arrays, and those ***declared directly in a namespace (outsize any function)***, are always initialized. If no explicit initializer is specified, all the elements are default-initialized ( with zeroes, for fundamental types).

    • Accessing the values of an array

      The values of any of the elements in an array can be accessed just like the value of a regular variable of the same type. The syntax is :

      name [index]

      foo[2] = 75;
      x = foo[2];
      

      Accessing out-of-range elements do not cause errors on compilation, but can cause errors on runtime.

      Bracket [] has two uses performing two different tasks:

      • One is to specify the size of arrays when they are declared
      • Second one is to specify indeices for concrete array elements when they are accessed.
      int foo[5];
      foo[2] = 75;
      
    • Multidimensional arrays

      Multidimensional arrays can be described as “arrays of arrays”. For example, a bidimensional array can be imagined as a two-dimensional table made of elements, all of them of a same uniform data type.

      int jimmy[3][5]; // a bidimensinal array of 3 per 5 elements of type int
      

      the way to reference the second element vertically and fourth horizontally in an expression would be:

      jimmy[1][3]
      

      Multidimensinal arrays are just an abstraction for programmers, since the same results can be achieved with a simple array, by multiplying its indices:

      int jimmy [3][5]; // is equivalent to
      int jimmy [15];   // (3 * 5 = 15)
      
  • Character sequence

    strings are, in fact, sequences of characters, we can represent them also as plain arrays of elements of a character type.

    By convention, the end of strings represented in character sequences is signaled by a special character: the null character, whose literal value can be written as '\0' (backslash, zero).

    • Initialization of null-terminated character sequences

      Sequences of characters enclosed in double-quotes (") are literal constants. And their type is , in fact, a null-terminated array of characters.

      Once string literal has been declared(initialized), they cannot be assigned values,

      myword = "bye";
      // equal to
      myword[] = {'b', 'y', 'e'};
      // would not be valid 
      myword = {'B','y','e', '\0'};
      // each of its elements can be assigned a value individually
      myword[0] = 'B';
      
      
    • Strings and null-terminated character sequences

      Plain arrays with null-terminated sequences of characters are the typical types used in the C language to represent strings, which is known as C-stings, difference from from the standard library defined specific type for strings (class string).

      The string literals still always produce null-terminated character sequences, not string objects.

      cin and cout support null-terminated sequences directly.

      strings have a dynamic size determind during runtime, while the size of arrays is determined on the compilation, before the program runs.

  • Pointers

    Variables can be explained as locations in the computer’s memory which can be accessed by their identifier (their name).

    When a variable is declared, the memory needed to store its value is assigned a specific location in memory (its memory address). An OS decides the particular memory locations on runtime.

    • Address-of operator (&)

      The address of a variable can be obtained by preceding the name of a variable with an ampersand sign (&), known as address-of operator.

      foo = &myvar;
      
      

      This would assign the address of variables myvar to foo.

      The actual address of a variable in memory cannot be known before runtime.

      The variable that stores the address of another variable (like foo in the previous example) is what in C++ is called a pointer.

      Pointers are a very powerful feature of the language that has many uses in lower level programming.

    • Dereference operator (*)

      Pointers are said to "point to " the variable whose address they store.

      An interesting property of pointers is that they can be used to access the variable they point to directly. This is done by preceding the pointer name with the dereference operator (*). The operater itself can be read as “value pointed to by”.

      baz = foo; // baz equal to foo (1776)
      baz = *foo; // baz equal to value pointed to by foo (25)
      
      

      The reference and dereference operators are thus complementary:

      • & is the address of operator, and can be read simply as “address of
      • * is the dereference operator, and can be read as “value pointed to by

      Thus, they have sort of opposite meanings : An address obtained with & can be dereferenced with *.

    • Declaring pointers

      Due to the ability of a pointer to directly refer to the value that it points to, a pointer has different properties when it points to a char than when it points to an int or a float. Once dereferenced , the type needs to be known. And for that, the declaration of a pointer needs to include the data type the pointer is going to point to.

      The declaration of pointers follows this syntax:

      type * name;

      where type is the data type pointed to by the pointer. This type is not the type of the pointer itself, but the data the pointer points to.

      int *number;
      
      

      The asterisk(*) used when declaring a pointer only means that it is a pointer (it is part of its type compound specifier), and should not be confused with the dereference operator seen a bit earlier.

      int * p1, *p2; // two pointers
      int * pi, p2;  // p1 is pointer, p2 is int type, spaces do not matters
      
      
    • Pointers and arrays
      int myarray[20];
      int * mypointer;
      mypointer = myarray;
      myarray = mypointer; // not valid
      
      

      mypointer and myarray would be equivalent and would have very similar properties. The main difference being that mypointer can be asssigned a different address, whereas myarray can never be assigned anything, and will always represent the same block of 20 elements of type int.

      a[5] = 0;
      *(a+5)=0;
      
      

      Brackets([]) were explained as specifying the index of an index of an element of the array.

      In fact these brackets are adereferenceing operator known as offset operator. They dereference the variable they follow just as * does, but they also add the number between brackets to the address being dereferenced.

    • Pointer initialization
      int myvar;
      int * myptr = &myvar; // pointers can be initialized either to the address of a variable
      int * bar = myptr; // or the value of another pointer(or array) 
      
      

      Pointers can be initialized to point to specific locations at the veru moment they are defined.

    • Pointer arithmetics

      Only addition and subtraction operations are allowed, the other make no sense in the world of pointers. But both addition and subtraction have a slightly different behavior with pointers, according to the size of the data type to which they point.

      When fundamental data types were introduce, we saw that types have different sizes.

      When adding one to a pointer is made to point to the following element of the same type, and, therefore, the size in bytes of the type it points to is added to the pointers.

      char * mychar; // 1byte
      short * myshort; // 2bytes
      ++mychar ; // add 1
      ++myshort; // add 2
      mychar = mychar + 1; // add 1
      myshort = myshort + 1; // add 2
      
      

      This is applicable both when adding and subtracting any number to a pointer.

      Regarding the increment (++) and decrement (--) operators, they both can be used as either prefix or suffix of an expression, with a slight difference in behavior: as a prefix, the increment happens before the expression is evaluated, and as a suffix, the increment happens after the expression is evaluated.

      The postfix operators such as increment and decrement have higher precedence than prefix operators, such as the dereference operator (*) .

      *p++;
      // equal to 
      *(p++);
      
      

      What it does is to increase the value of p (so it now points to the next element), but because ++ is used as postfix, the whole expression is evaluated as the value pointed orginally by the the pointer (the address it pointed to before being incremented).

    • Pointers and const

      Pointers can be used to access a variable by its address, and this access may include modifying the value pointed. But it is also possible to declare pointer that can access the pointed value to read it, but not to modify it. But not to modify it. For this, it is enough with qualifying the type pointed to by the pointer as const.

      int x;
      int y = 10;
      const int* p = &y;
      x = *p;
      *p = x; // error: modifying p, which is const-qualified
      
      

      A pointer to non-const can be implicityly converted to a pointer to const, But not the other way around!

      A function that takes a pointer to non-const as parameter can modify the value passed as argument, while a function that takes a pointer to const as parameter cannot.

            int *    p1 = &x; // non-const pointer to non-const int 
      const int *    p2 = &x; // non-const pointer to const int 
      int const *    p2_2 = &x; // same as above
            int * const p3 = &x; // const pointer to non-const int 
      const int * const p4 = &x; // const pointer to const int
      
      

      The syntax with const and pointers is definitely tricky, and recognizing the cases that best suit each use tends to requeire some experience.

    • Pointers and sting literals

      String literal are arrays containing null-terminate character sequences .

      const char * foo = "hello";
      
      

      This declares an array with the literal representation for “hello”, and then a pointer to its first element is assigned to foo.

    • Pointers to pointers

      C++ allows the use of pointers that point to pointers, that these, in its turn, point to data (or even to other pointers). The syntax simply requires an asterisk (*) for each level of indirection in the declaration of the pointer:

      char a;
      char * b;
      char ** c;
      a = 'z';
      b = &a;
      c = &b;
      
      

      each one of them would correspond to a different value:

      • c is of type char** and a value of 8092;
      • *c is of type char* and a value of 7230;
      • **c is of type char and a value of ‘z’;
    • void pointers

      The void type of pointer is a special type of pointer.

      In C++, void represents the absence of type.

      Therefore, void pointers are pointers that point to a value that has no type (and thus also an undetermind length and undetermined dereferencing properties).

      sizeof is an operator integrated in the C++ language that returns the size in bytes of its argument. For non-dynamic data types, this value is a constant. Therefore, for eaample, sizeof(char) is 1, because char has always a size of one byte.

    • Invalid pointers and null pointers

      In principle, pointers are meant to point to valid addresses, such as address of a variable or the address of an element in an array. But pointers can actually point to any address, including addresses that do not refer to any valid element. Typical examples of this are uninitialized pointers and pointers to nonexistent elements of an array:

      int *p;  // uninitialized pointer (local variable)
      int myarray[10];
      int *q = myarray+20;  // element out of bounds
      
      

      Neither p nor q point to addresses known to contain a value, but none of the above statements causes an error. In C++, pointers are allowed to take any address value, no matter whether there actually is something at that address or not.

      What can cause an error is to dereference such a pointer (i.e., actually accessing the value they point to). Accessing such a pointer causes undefined behavior, ranging from an error during runtime to accessing some random value.

      But, sometimes, a pointer really needs to explicitly point to nowhere, and not just an invalid address. For such cases, there exists a special value that any pointer type can take: the null pointer value. This value can be expressed in C++ in two ways: either with an integer value of zero, or with the nullptr keyword:

      int * p = 0;
      int * q = nullptr;
      
      

      Both p and q are null pointers, meaning that they explicitly point to nowhere, and they both actually compare equal: all null pointers compare euqal to other null pointers. It is also quite usual to see the defined constant NULL be used in older code to refer to the null pointer value:

      int * r = NULL;
      
      

      NULL is defined in several headers of the standard library. and is defined as an alias of some null pointer constant value (such as 0 or nullptr).

      Do not confuse null pointers with void pointers!

      A null pointer is a value that any pointer can take to represent that it is pointing to “nowhere”, while a void pointer is a type of pointer that can point to somewhere without a specific type.

    • Pointers to functions

      C++ allows operations with pointers to functions. The typical use of this is for passing a function as an argument to another function.

      Pointers to functions are declared with the same syntax as a regular function declaration, except that the name of the function is enclosed between parentheses() and an asterisk(*) is inserted before the name:

      int operation (int x, int y, int (*functicall)(int, int))
      {
          int g;
          g = (*functocall)(x, y);
          return (g);
      }
      
      int main()
      {
          int m, n;
          int (*minus)(int, int) = subtraction;
          m = operation(7, 5, addition);
          n = operation(20, m, minus);
      }
      
      

      minus is a pointer to a function that has two parameters of type int. It is initialized to point to the function subtraction:

      int (* minus)(int, int) = subtraction;
      
      
  • Dynamic memory

    All memory needs were determind before program execution by defining the variables needed. But there may be cases where the memory needs of a program can only determined during runtime. For example , thern the memory needed depends on user input. On these cases, programs need to dynamically allocate memory, for which the C++ language integrates the operators new and delete.

    • Operators new and new[]

      Dynamic memory is allocated using operator new.

      new is followed by a data type specifier and , if a sequence of more than one element is required, the number of these within []. It returns a pointer to the beginning of the new block of memory allocated. Its syntax is :

      pointer = new type
      pointer = new type [number of elements]
      
      
      int * foo; // 定义一个指针
      foo = new int [5]; 
      
      

      foo is a pointer, and thus , the first element pointed to by foo can be accessed either with the expression foo[0] or the expression *foo (both are equivalent). The second element can be accessed either with foo[1] or *(foo+1).

      There is a substantial difference between declaring a normal array and allocating dynamic memory for a block of memory using new. The most important differece is that the size of a regualr array needs to be a constant expression, and thus its size has to be determined at the moment of designing the program, before it run, whereas the dynamic memory allocation performed by new allows to assign memory dyring runtime using any variable value as size.

      C++ provides two standard mechanisms to check if the allocation wa successful:

      • one is by handling exceptions which named bad_alloc is thrown and the program execution is terminated. This exception method is the method used by default by new
      • The other method is known as nothrow, the pointer returned by new is a null pointer, and the program continues its execution normally
    • Operators delete and delete[]

      In most cases, memory allocated dynamically is only needed during specific periods of time within a program; once it is no longer needed, it can be freed so that the momeory becomes available again for other requests of dynamic memory. This is the purpose pf operator delete, whose syntax is:

      delete pointer;
      delete[] pointer;
      
      

      The value passed as argument to delete shall be either a pointer to a memory block previously allocated with new, or a null pointer.

      It is considerd good practice for programs to always be able to handle failures to allocate memory, either by checking the pointer value (if nothrow) or by catching the proper exception.

    • Dynamic memory in C

      new and delete for allocating dynamic memory in C++ were not available in the C language.

  • Data Structures

    • Data structures

      A data structure is a group of data elements grouped together under one name. These data elements, known as members, can have different types and different length. Data structures can be declared in C++ using the following syntax:

      struct type_name{
          member_type1 member_name1;
          member_type2 member_name2;
          member_type3 member_name3;
          .
          .
      } object_names;
      
      

      t y p e _ n a m e type\_name is a name for the structure type;

      o b j e c t _ n a m e object\_name can be a set of valid identifiers for objects that have the type of this structure.

      struct product {
          int weight;   // member_type member_name
          double price;
      };
      product apple;
      produce banana, melon;
      
      // equal to 
      struct product {
          int weight;
          double price;
      } apple, banana, melon;
      
      

      Once the three objects of a determined structure type are declared , its members can be accessed dirctly. The syntax for that is simply to insert a dot (.) between the object name and the member name.

      One of the features of data structures is the ability to refer to both their members individually or to the entire structure as a whole.

    • Pointers to structures

      Like any other type, structures can be pointed to by its own type of pointers:

      struct movies_t{
          string title;
          int year;
      };
      
      movies_t amovie;
      movies_t * pmovie;
      
      pmovie = &amovie;
      
      

      The arrow operator (->) is a dereference operator that is used exclusively with pointers to objects that have members.

      This operator serves to access the member of an object directly from its address. For example :

      pmovie -> title; // 从指针到其所指对象的member,整体作为一个变量,返回这个title的值
      
      

      is, for all purposes, equivalent to :

      *pmovie.title
      // equal to 
      *(pmovie.title)
      
      
    • Nesting Structures

      Structures can also be nested in such a way that an element of a structure is itself another structure.

  • Other data types

  • Type aliases (typedef / using)

    A type alias is a different name by which a type can be identified. In C++, any valid type can be aliased so that it can be referred to with a different identifier.

    In C++, there are two syntaxes for creating such type aliases:

    • Inherited form the C language, uses the typedef keyword

      typedef existing_type new_type_name;
      // existing_type is any type, either fundamental or compound
      typedef char C;
      typedef char * pChar;
      
      
    • using new_type_name = existing_type;

      using C = char;
      using pChar = char *;
      
      

    using is more generic, because typedef has certain limitaions in the realm of templates.

  • Unions

    Unions allow one portion of memory to be accessed as different data types. Its declaration and use is similar to the one of structures, but its functionality is totally different:

    union type_name{
        member_type1 member_name1;
        member_type2 member_name2;
        ...
    } object_names;
    
    

    This creates a new union type, identifid by type_name, in which member elements occupy the same physical space in memory. The size of this type is the one of the largest member element.

    Each of these memebrs is of a different data type, But since all of them are referring to the same location in memory, the modification of one of the members will affect the value of all of them. It is not possible to store different values in them in a way that each is independent of the others.

  • Anonymous unions

    When unions are members of a class (or structure), they can be declared with no name. Which means they become anonymous unions.

  • Enumerated types (enum)

    Enumerated types are types that are defined with a set of custom identifiers, known as enumerators, as possible values. Objects of these enumerated types can take any of these enumerators as value.

    enum type_name {
        value1,
        value2,
        ...
    } object_names;
    
    

    This declaration includes no other type, neither fundamental nor compound, in its definition, which means this creates a whole new data type from scratch withou basing it on any other existing type.

  • Enumerated types with enum class

    To create real enum type which are neither implicitly convertible to int and that neither have enumerator values of type int, but the enum type itself, usding enum class (or enum struct) instead of enum:

    enum class Colors {
        black,
        blue,
        ..
    };
    
    

猜你喜欢

转载自blog.csdn.net/The_Time_Runner/article/details/107304683