C++ Basic Features - Type

Every widely used programming language provides a common set of features, which differ in detail from one language to another, this post briefly show the basic feature.

Variables and basic type

Type are fundamental to every programming language: the tell us what the data mean and what operations we can perform on those data.

Primitive Built-in Types

C++ defines a set of Primitive Built-in Types that include arithmetic types and a special type called void, the arithmetic types represent character, integer, boolean values, floating-point numbers. The void type has no associated values and can be used in only a few circumstances, most commonly as the return type for functions that do not return a value. This table give the normally used types and details.

type meaning size
int integer 32 bits
long long long integer 64 bits
float single-precision-floating 32 bits
double double-precision-floating 64 bits
long double extended-precision-floating 128 bits
char character 8 bits
bool Boolean 8 bits
#include <iostream>

int main()
{
    //you can use this statement check how many bytes for the type
    //change int to another type you want
    std::cout << sizeof(int) << std::endl;
    return 0;
}

The type of an object defines the data that an object might contain and what operations that object can perform. Among the operations that many types support is the ability to convert objects of the given type to other, related types. Type conversions happen automatically when we use an object of one type where an object of another type is expected.

#include <iostream>

int main()
{
    //only b = 0 can't make b is false
    bool b = 10; // b is true
    //actually, true equal to 1 and false equal to 0
    int i = b; // i has value 1
    //the fractional part will be lose
    i = 3.14; // i has value 3
    double pi = i; // pi has value 3.0
    unsigned char c = -1; // assuming 8-bit chars, c has value 255
    signed char c2 = 256; // assuming 8-bit chars, the value of c2 is undefined
    unsigned u = 10;
    int i = -42;
    //the type of sum result is int
    std::cout << i + i << std::endl; // prints -84
    //the type of sum result is unsigned
    //the int tpye has been convert to a unsigned before add operation
    std::cout << u + i << std::endl; // if 32-bit ints, prints 4294967264
    return 0;
}

Variables

A variable provides us with named storage that our programs can manipulate. Each variable in C++ has a type. The type determines the size and layout of the variable’s memory, the range of values that can be stored within that memory, and the set of operations that can be applied to the variable. C++ programmers tend to refer to variables as “variables” or “objects” interchangeably.

The previous codes has explained how define a variable, but they are initialized, if you define a variable without an initializer, what that default value is depends on the type of the variable and may also depend on where the variable is defined. Some classes require that every object be explicitly initialized. The compiler will complain if we try to create an object of such a class with no initializer.

The another notice is the difference between variable declarations and definitions. To allow programs to be written in logical parts, C++ supports what is commonly known as separate compilation. Separate compilation lets us split our programs into several files, each of which can be compiled independently. When we separate a program into multiple files, we need a way to share code across those files. A variable declaration specifies the type and name of a variable. A variable definition is a declaration. In addition to specifying the name and type, a definition also allocates storage and may provide the variable with an initial value.

//extern keyword
extern int i; // declares but does not define i
int j; // declares and defines j
extern double pi = 3.1416; // this is a error statement

Identifiers in C++ can be composed of letters, digits, and the underscore character. The language imposes no limit on name length. Identifiers must begin with either a letter or an underscore. Identifiers are case-sensitive; upper- and lowercase letters are distinct, The standard also reserves a set of names for use in the standard library. The identifiers we define in our own programs may not contain two consecutive underscores, nor can an identifier begin with an underscore followed immediately by an uppercase letter. In addition, identifiers defined outside a function may not begin with an underscore.

Conventions for Variable Names:

  1. An identifier should give some indication of its meaning
  2. Variable names normally are lowercase
  3. classes we define usually begin with an uppercase letter
  4. Identifiers with multiple words should visually distinguish each word, for example, student_loan or studentLoan

A scope is a part of the program in which a name has a particular meaning. Most scopes in C++ are delimited by curly braces. The same name can refer to different entities in different scopes. Names are visible from the point where they are declared until the end of the scope in which the declaration appears.

#include <iostream>
//The name main is defined outside any curly braces
//names defined outside a function has global scope
int a = 0;
int main()
{
    int sum = 0;
    // sum values from 1 through 10 inclusive
    for (int val = 1; val <= 10; ++val)
        sum += val; // equivalent to sum = sum + val
    std::cout << "Sum of 1 to 10 inclusive is " << sum << std::endl;
    return 0;
}

Scopes can contain other scopes. The contained (or nested) scope is referred to as an inner scope, the containing scope is the outer scope. Once a name has been declared in a scope, that name can be used by scopes nested inside that scope. Names declared in the outer scope can also be redefined in an inner scope, this is a simple case.

#include <iostream>
// Program for illustration purposes only: It is bad style for a function
// to use a global variable and also define a local variable with the same name
int reused = 42; // reused has global scope
int main()
{
     int unique = 0; // unique has block scope
     // output #1: uses global reused; prints 42 0
     std::cout << reused << " " << unique << std::endl;
     int reused = 0; // new, local object named reused hides global reused
     // output #2: uses local reused; prints 0 0
     std::cout << reused << " " << unique << std::endl;
     // output #3: explicitly requests the global reused; prints 42 0
     std::cout << ::reused << " " << unique << std::endl;
     return 0;
}

Compound Types

A compound type is a type that is defined in terms of another type. C++ has several compound types, two of which—references and pointers—we’ll cover in this chapter.

A reference defines an alternative name for an object. A reference type “refers to” another type. We define a reference type by writing a declarator of the form &d, where d is the name being declared: Once initialized, a reference remains bound to its initial object. There is no way to rebind a reference to refer to a different object. Because there is no way to rebind a reference, references must be initialized.

#include <iostream>

int main()
{
    int ival = 1024;
    int& refVal = ival; // refVal refers to (is another name for) ival
    refVal = 100;
    std::cout << ival << " " << refVal << std::endl; // will print 100 100
    //error statement examples 
    int &refVal2; // error: a reference must be initialized
    int &refVal4 = 10; // error: initializer must be an object
    double dval = 3.14;
    int &refVal5 = dval; // error: initializer must be an int object
    return 0;
}

A pointer is a compound type that “points to” another type. Like references, pointers are used for indirect access to other objects. Unlike a reference, a pointer is an object in its own right. Pointers can be assigned and copied; a single pointer can point to several different objects over its lifetime. Unlike a reference, a pointer need not be initialized at the time it is defined. Like other built-in types, pointers defined at block scope have undefined value if they are not initialized. the next case show how to use pointer:

#include <iostream>

int main()
{
    // create a empty pointer
    int* ptr = nullptr;
    // ptrToN holds the address of n; ptrToN is a pointer to n
    //We get the address of an object by usin the address of operator 
    //(the & operator)
    //note this is different from the reference
    int n = 10;
    int* ptrToN = &n;
    //print the address of n and value
    std::cout << "address: " << ptrToN
        << " value: " << *ptrToN << std::endl;
    //cahnge value of n by pointer, will print 100
    *ptrToN = 100;
    std::cout << n << std::endl;
    //point to pointer
    int ival = 1024;
    int *pi = &ival; // pi points to an int
    int **ppi = &pi; // ppi points to a pointer to an int
    return 0;
}

(notice: Because references are not objects, they don’t have addresses. Hence, we may not define a pointer to a reference.)

The type void* is a special pointer type that can hold the address of any object. Like any other pointer, a void pointer holds an address, but the type of the object at that address is unknown, so we cannot use a voidto operate on the object it addresses—we don’t know that object’s type, and the type determines what operations we can perform on the object. Generally, we use a void* pointer to deal with memory as memory, rather than using the pointer to access the object stored in that memory.

const Qualifier

Sometimes we want to define a variable whose value we know cannot be changed. We can make a variable unchangeable by defining the variable’s type as const. Because we can’t change the value of a const object after we create it, it must be initialized.

const int n = 512;
n = 1024;  // error: attempt to write to const object
const int k; //error: k is uninitialized const

Sometimes we have a const variable that we want to share across multiple files, to share a const object among multiple files, you must define the variable as extern.

As with any other object, we can bind a reference to an object of a const type. To do so we use a reference to const, which is a reference that refers to a const type. Unlike an ordinary reference, a reference to const cannot be used to change the object to which the reference is bound:

const int ci = 1024;
const int &r1 = ci; // ok: both reference and underlying object are const
int &r2 = ci; // error: non const reference to a const object

we noted that there are two exceptions to the rule that the type of a reference must match the type of the object to which it refers.

//this code can be compilation
double dval = 3.14;
const int &ri = dval;
//actually const int &ri = dval; will be think as this
//const int temp = dval; // create a temporary const int from the double
//const int &ri = temp;  //const int &ri = temp; 

As with references, we can define pointers that point to either const or nonconst types. Like a reference to const, a pointer to const may not be used to change the object to which the pointer points. Next case show details.

const double pi = 3.14;   // pi is const; its value may not be changed
double *ptr = &pi;        // error: ptr is a plain pointer
const double *cptr = &pi; // ok: cptr may point to a double that is const
*cptr = 42;               // error: cannot assign to *cptr
double dval = 3.14;       // dval is a double; its value can be changed
cptr = &dval;             // ok: but can't change dval through cptr
*cptr = 5472;             // error: cannot assign to *cptr

Unlike references, pointers are objects. Hence, as with any other object type, we can have a pointer that is itself const. Like any other const object, a const pointer must be initialized, and once initialized, its value (i.e., the address that it holds) may not be changed.

int errNumb = 0;
int* const curErr = &errNumb; // curErr will always point to errNumb
const double pi = 3.14159;
const double* const pip = &pi; // pip is a const pointer to a const object

We use the term top-level const to indicate that the pointer itself is a const. When a pointer can point to a const object, we refer to that const as a low-level const.

Dealing with Types

A type alias is a name that is a synonym for another type. Type aliases let us simplify complicated type definitions, making those types easier to use. We can define a type alias in one of two ways. Traditionally, we use a typedef, the new standard introduced a second way to define a type alias, via an alias
declaration:

typedef long long ll;   // ll is a synonym for long long
typedef long long *ptr; // ptr for long long*
using ll = long long;   // ll is a synonym for long long

Under the new standard, we can let the compiler figure out the type for us by using the auto type specifier. Unlike type specifiers, such as double, that name a specific type, auto tells the compiler to deduce the type from the initializer. As with any other type specifier, we can define multiple variables using auto. Because a declaration can involve only a single base type, the initializers for all the variables in the declaration must have types that are consistent with each other:

int n1 = 10, n2 = 20;
double n3 = 3.5, n4 = 9.5;
//type of item from the type returned by applying + to val1 and val2.
auto s1 = n1 + n3;  // s1's type is double
auto s2 = n1 + n2;  // s2;s type is int
auto k1 = 10, k3 = 19;   // ok
auto k2 = 10, *k4 = &k2; // ok
//error C++ 'auto' type is for this entity, but was previously implied to be int
auto k5 = 10, k6 = 3.14; 

Sometimes we want to define a variable with a type that the compiler deduces from an expression but do not want to use that expression to initialize the variable. For such cases, the new standard introduced a second type specifier, decltype, which returns the type of its operand.

const double m = 90.99, &r = m, * p = &m;
decltype(m) ap = 184;           // ap has type const int
decltype(r) x = m;              // x has type const int& and is bound to x
decltype(m) y;                  // error: y is a const and must be initialized
decltype(r) z;                  // error: z is a reference and must be initialized
decltype(*p) c;                 // error: c is int& and must be initialized

// decltype of a parenthesized variable is always a reference
decltype((m)) d; // error: d is double& and must be initialized
decltype(m) e; // ok: e is an (uninitialized) double

Defining Our Own Data Structures

In C++ we define our own data types by defining a class. The library types string, istream, and ostream are all defined as classes, we will tell how to defined a full class in next several chapters, first we create a class that does not support any operations.

struct Sales_data {
    std::string bookNo;
    unsigned units_sold = 0;
    double revenue = 0.0;
};

Our class begins with the keyword struct, followed by the name of the class and a (possibly empty) class body. The class body is surrounded by curly braces and forms a new scope. The names defined inside the class must be unique within the class but can reuse names defined outside the class. The close curly that ends the class body must be followed by a semicolon. The semicolon is needed because we can define variables after the class body:

struct Sales_data { /* ... */ } accum, trans, *salesptr;
// equivalent, but better way to define these objects
struct Sales_data { /* ... */ };
Sales_data accum, trans, *salesptr;

Generally, we define a class in a separate header file, typically, classes are stored in headers whose name derives from the name of the class. Headers often need to use facilities from other headers. For example,
because our Sales_data class has a string member, Sales_data.h must #include the string header. As we’ve seen, programs that use Sales_data also need to include the string header in order to use the bookNo member.

As a result, programs that use Sales_data will include the string header twice: once directly and once as a side effect of including Sales_data.h. Because a header might be included more than once, we need to write our headers in a way that is safe even if the header is included multiple times.

The most common technique for making it safe to include a header multiple times relies on the preprocessor. The preprocessor—which C++ inherits from C—is a program that runs before the compiler and changes the source text of our programs. Our programs already rely on one preprocessor facility, #include. When the preprocessor sees a #include, it replaces the #include with the contents of the specified header.

C++ programs also use the preprocessor to define header guards. Header guards rely on preprocessor variables. Preprocessor variables have one of two possible states: defined or not defined. The #define directive takes a name and defines that name as a preprocessor variable. There are two other directives that test whether a given preprocessor variable has or has not been defined: #ifdef is true if the variable has been defined, and #ifndef is true if the variable has not been defined. If the test is true, then everything following the #ifdef or #ifndef is processed up to the matching #endif.

//Sales_data.h
#ifndef SALES_DATA_H
#define SALES_DATA_H
#include <string>
struct Sales_data {
    std::string bookNo;
    unsigned units_sold = 0;
    double revenue = 0.0;
};
#endif

The first time Sales_data.h is included, the #ifndef test will succeed. The preprocessor will process the lines following #ifndef up to the #endif. As a result, the preprocessor variable SALES_DATA_H will be defined and the contents of Sales_data.h will be copied into our program. If we include Sales_data.h later on in the same file, the #ifndef directive will be false. The lines between it and the #endif directive will be ignored. this is a simple case to statistics sales of book:

#include <iostream>
#include <string>
#include "Sales_data.h"

int main()
{
    Sales_data book1, book2;
    double price = 0;
    // read the first transactions: ISBN, number of books sold, price per book
    std::cin >> book1.bookNo >> book1.units_sold >> price;
    book1.revenue = price * book1.units_sold;
    // read the second transactions: ISBN, number of books sold, price per book
    std::cin >> book2.bookNo >> book2.units_sold >> price;
    book2.revenue = price * book2.units_sold;
    //add operation
    if (book1.bookNo == book2.bookNo) {
        unsigned totalCnt = book1.units_sold + book2.units_sold;
        double totalRevenue = book1.revenue + book2.revenue;
        // print: ISBN, total sold, total revenue, average price per book
        std::cout << book1.bookNo << " " << totalCnt
            << " " << totalRevenue << " ";
        if (totalCnt != 0)
            std::cout << totalRevenue / totalCnt << std::endl;
        else
            std::cout << "(no sales)" << std::endl;
        return 0;
    }
    else {
        std::cout << "there are different books" << std::endl;
        return -1;
    }
}

Strings, Vectors, and Arrays

In addition to the built-in types covered in previous, C++ defines a rich library of abstract data types. Among the most important library types are string, which supports variable-length character strings, and vector, which defines variable-size collections. Associated with string and vector are companion types known as iterators, which are used to access the characters in a string or the elements in a vector.

Before beginning our exploration of the library types, we’ll look at a mechanism for simplifying access to the names defined in the library.

Namespace using Declarations

Up to now, our programs have explicitly indicated that each library name we use is in the std namespace. For example, to read from the standard input, we write std::cin. std::cin says that we want to use the name cin from the namespace std. there are easier ways to use namespace members. The safest way is a using declaration.

#include <iostream>
// using declaration; when we use the name cin, we get the one from the namespacestd
using std::cin;
int main()
{
    int i;
    cin >> i; // ok: cin is a synonym for std::cin
    cout << i; // error: no using declaration; we must use the full name
    std::cout << i; // ok: explicitly use cout from namepsace std
    return 0;
}

Note “Headers Should Not Include using Declarations”, The reason is that the contents of a header are copied into the including program’s text. If a header has a using declaration, then every program that includes that header gets that same using declaration.

Library string Type

Each class defines how objects of its type can be initialized. A class may define many different ways to initialize objects of its type. examples:

#include <iostream>
#include <string>
using std::string;

int main()
{
    string s1;          // default initialization; s1 is the empty string
    string s2 = s1;     // s2 is a copy of s1
    string s3 = "hello";// s3 is a copy of the string literal
    string s4(10, 'A'); // s4 is AAAAAAAAAA
    string s5(s1);      // s5 is a copy of s1
    string s6("hello"); // s6 is a copy of the string literal
    return 0;
}

When we initialize a variable using =, we are asking the compiler to copy initialize the object by copying the initializer on the right-hand side into the object being created. Otherwise, when we omit the =, we use direct initialization.

string s5 = "hiya"; // copy initialization
string s6("hiya");  // direct initialization
string s7(10, 'c'); // direct initialization; s7 is cccccccccc

Along with defining how objects are created and initialized, a class also defines the operations that objects of the class type can perform. The string input operator reads and discards any leading whitespace (e.g., spaces, newlines, tabs). It then reads characters until the next whitespace character is encountered. which mean when we read a string containing whitespace will cause error. we can use the getline function instead of the >> operator. The getline function takes an input stream and a string.

//input operations and output operations
string str; //initlize a empty string
cin >> str; //read from istream
cout << str << endl; //write to output
getline(cin, str);   //read a line
cout << str << endl; //write to output

Sometimes we want get the information of string, such as if empty and the size of string, The empty function does what one would expect: It returns a bool indicating whether the string is empty. The size member returns the length of a string. It might be logical to expect that size returns an int or unsigned. Instead, size returns a string::size_type value.The string class—and most other library types—defines several companion types.
These companion types make it possible to use the library types in a machine independent manner. The type size_type is one of these companion types. To use the size_type defined by string, we use the scope operator to say that the name size_type is defined in the string class. Admittedly, it can be tedious to type string::size_type. Under the new standard, we can ask the compiler to provide the appropriate type by using auto or decltype

auto emp = str.empty(); //check empty
auto len = str.size();  //get size

The string class defines several operators that compare strings. These operators work by comparing the characters of the strings. The comparisons are case sensitive, uppercase and lowercase versions of a letter are different characters.

// all compare operation
cout << (str1 == str2) << endl;
cout << (str1 != str2) << endl;
cout << (str1 >= str2) << endl;
cout << (str1 <= str2) << endl;
cout << (str1 > str2) << endl;
cout << (str1 < str2) << endl;
//Adding Two strings
string s1 = "hello, ", s2 = "world\n";
string s3 = s1 + s2; // s3 is hello, world\n
s1 += s2; // equivalent to s1 = s1 + s2
//Adding Literals strings and strings
string s1 = "hello", s2 = "world"; 
string s3 = s1 + ", " + s2 + '\n'; // ok
string s4 = s1 + ", "; // ok: adding a string and a literal
string s5 = "hello" + ", "; // error: no string operand
string s6 = s1 + ", " + "world"; // ok: each + has a string operand
string s7 = "hello" + ", " + s2; // error: can't add string literals

For historical reasons, and for compatibility with C, string literals are not standard library strings. It is important to remember that these types differ when you use string literals and library strings.


Often we need to deal with the individual characters in a string. We might want to check to see whether a string contains any whitespace, or to change the characters to lowercase, or to see whether a given character is present, and so on.

The one part of processing characters is knowing and/or changing the characteristics of a character. This part of the job is handled by a set of library functions, described in next Table. These functions are defined in the cctype header.

function name meanning
isalnum Check if character is alphanumeric
isalpha Check if character is alphabetic
isblank Check if character is blank
iscntrl Check if character is a control character
isdigit Check if character is decimal digit
isgraph Check if character has graphical representation
islower Check if character is lowercase letter
isprint Check if character is printable
ispunct Check if character is a punctuation character
isspace Check if character is a white-space
isupper Check if character is uppercase letter
isxdigit Check if character is hexadecimal digit
tolower Convert uppercase letter to lowercase
toupper Convert lowercase letter to uppercase

Another of this kind of processing involves how we gain access to the characters themselves. Sometimes we need to process every character. Other times we need to process only a specific character, or we can stop processing once some condition is met.

If we want to do something to every character in a string, by far the best approach is to use a statement introduced by the new standard: the range for statement.

string str = "hello!!!";
for (auto c : str) {   // for every char in str
     cout << c << endl; // print the current character followed by a newline
}
//count the number of punctuation
decltype(str.size()) punct = 0;
for (auto c : str) {
     if (ispunct(c)){ // if the character is punctuation
          ++punct;
     }
}
cout << punct << endl;

If we want to change the value of the characters in a string, we must define the loop variable as a reference type, Remember that a reference is just another name for a given object.

string str = "hello!!!";
for (auto &c : str) {
     c = toupper(c); // c is a reference, so the assignment changes the char in str
}
cout << str << endl;

A range for works well when we need to process every character. However, sometimes we need to access only a single character or to access characters until some condition is reached. There are two ways to access individual characters in a string: We can use a subscript or an iterator. The subscript operator (the [ ] operator) takes a string::size_type value that denotes the position of the character we want to access. Subscripts for strings start at zero, so str[0] is the first character, and the last character is in str[str.size() - 1]. we can rewrite previous as this:

for (decltype(str.size()) i = 0; i < str.size(); i++)
{
     str[i] = toupper(str[i]);
}
cout << str << endl;

When we use a subscript, we must ensure that the subscript is in range. That is, the subscript must be >= 0 and < the size() of the string. One way to simplify code that uses subscripts is always to use a variable of type string::size_type as the subscript. Because that type is unsigned, we ensure that the subscript cannot be less than zero.

Library vector Type

A vector is a collection of objects, all of which have the same type. Every object in the collection has an associated index, which gives access to that object. A vector is often referred to as a container because it “contains” other objects.

A vector is a class template. C++ has both class and function templates. Writing a template requires a fairly deep understanding of C++. Indeed, we won’t see how to create our own templates now! Fortunately, we can use templates without knowing how to write them.

For a class template, we specify which class to instantiate by supplying additional information, the nature of which depends on the template. How we specify the information is always the same: We supply it inside a pair of angle brackets following the template’s name. Note vector is a template, not a type. Types generated from vector must include the element type, for example, vector<int>.

#include <string>
#include <vector>
using std::string;
using std::vector;

int main()
{
    vector<int> ivec;             //ivec holds objects of type int
    vector<vector<string>> strvec;//strvec holds objects of type vector<string>
    return 0;
}

As with any class type, the vector template controls how we define and initialize vectors. Next code lists the most common ways to define vectors.

#include <vector>
using std::vector;

int main()
{
    vector<int> ivec1; //default initlization, ivec1 is empty
    vector<int> ivec2 = ivec1; //ivec2 is copy of ivec1
    vector<int> ivec3(ivec1);  //ivec3 is copy of ivec1
    vector<int> ivec4(5, 1);   //ivec4 have 5 element with value 1
    vector<int> ivec5(5);      //ivec5 have 5 element with value initliazated
    vector<int> ivec6{ 1,2,3,4,5 };   //ivec6 have 1,2,3,4,5
    vector<int> ivec7 = { 1,2,3,4,5 };//ivec7 have 1,2,3,4,5
    return 0;
}

When we use parentheses, we are saying that the values we supply are to be used to construct the object. Thus, ivec4 and ivec5 use their initializers to determine the vector’s size, and its size and element values, respectively.

When we use curly braces, {…}, we’re saying that, if possible, we want to list initialize the object. That is, if there is a way to use the values inside the curly braces as a list of element initializers, the class will do so. Only if it is not possible to list initialize the object will the other ways to initialize the object be considered.

vector<string> ivec8{10, "hi"}; // ivec8 has ten elements with value "hi"
vector<string> ivec9{10};       // ivec9 has ten default-initialized elements

Directly initializing the element of a vector just feasible only if we have a small number of known initial values or we want make a copy of a vector. More commonly, we use the push_back method add elements when running. The push_back operation takes a value and “pushes” that value as a new last
element onto the “back” of the vector. This code will add 0 to 99 to a vector in sequence:

vector<int> ivec1; //default initlization, ivec1 is empty
for (int i = 0; i < 100; i++){
     ivec1.push_back(i); // add to ivec1
}

The standard requires that vector implementations can efficiently add elements at run time. Because vectors grow efficiently, it is often unnecessary and can result in poorer performance to define a vector of a specific size. Note we cannot use a range for if the body of the loop adds elements to the vector. The body of a range for must not change the size of the sequence over which it is iterating.

In addition to push_back, vector provides only a few other operations, most of which are similar to the corresponding operations on strings. we can get full operations in here. We access the elements of a vector the same way that we access the characters in a string:

vector<int> v{1,2,3,4,5,6,7,8,9};
for (auto &i : v) // for each element in v (note: i is a reference)
     i *= i; // square the element value
for (auto i : v) // for each element in v
     cout << i << " "; // print the element
cout << endl;

The empty and size members behave as do the corresponding string members: empty returns a bool indicating whether the vector has any elements, and size returns the number of elements in the vector. The size member returns a value of the size_type defined by the corresponding vector type.

//note vector is only a template
vector<int>::size_type // ok
vector::size_type      // error

Programmers new to C++ sometimes think that subscripting a vector adds elements; it does not. The following code intends to add ten elements to ivec:

vector<int> ivec; // empty vector
for (decltype(ivec.size()) ix = 0; ix != 10; ++ix)
      ivec[ix] = ix; // disaster: ivec has no elements

However, it is in error: ivec is an empty vector; there are no elements to subscript! As we’ve seen, the right way to write this loop is to use push_back like previous.

Introducing Iterators

Although we can use subscripts to access the characters of a string or the elements in a vector, there is a more general mechanism—known as iterators—that we can use for the same purpose. All of the library containers have iterators, but only a few of them support the subscript operator.

Like pointers, iterators give us indirect access to an object. We can use an iterator to fetch an element and iterators have operations to move from one element to another. As with pointers, an iterator may be valid or invalid. A valid iterator either denotes an element or denotes a position one past the last element in a container. All other iterator values are invalid. Unlike pointers, we do not use the address-of operator to obtain an iterator. Instead, types that have iterators have members that return iterators. This code show getting vector iterators.

vector<int> v;
//In general, we do not care about the precise type that an iterator has.
//In this example, we used auto to define b and e
auto b = v.begin(), e = v.end();

The begin member returns an iterator that denotes the first element (or first character), The iterator returned by end is an iterator positioned “one past the end” of the associated container (or string). This iterator denotes a nonexistent element “off the end” of the container. If the container is empty, begin
returns the same iterator as the one returned by end.

Iterators support only a few operations, which are listed in next code:

#include <iostream>
#include <string>
#include <vector>
#include "Sales_data.h"
using std::cout; using std::cin; using std::endl;
using std::string;
using std::vector;

int main()
{
    vector<int> v{ 1,2,3,4,5,6,7,8,9 };
    //b++: Increments b to refer next element
    //b--: Decrements b to refer previous element
    //b != v.end(): Compare two iterator for inequality
    for (auto b = v.begin(); b != v.end(); b++){
        //*b get the reference to the element donated by b
        *b *= *b;
    }
    Sales_data k;
    k.bookNo = "0-321-71411-3";
    vector<Sales_data> sale = {k};
    auto sb = sale.begin(), se = sale.end();
    //equal to (*sb).bookNo will print 0-321-71411-3
    cout << sb->bookNo << endl;
    return 0;
}

Like pointer, there ere also has const iterator, when you define a const container(such as vector) or string, the begin and end members are const iterator. You also can use cbigin or cend to get a const iterator when you don not want change element.

Except previous operation, they are some operations for special container. Iterators for string and vector support additional operations that can move an iterator multiple elements at a time. They also support all the relational operators. These operations, which are often referred to as iterator arithmetic, are described in the next Table.

operation meaning
iter + n iter donate to next nth element
iter - n iter donate to front nth element
iter += n iter donate to next nth element
iter -= n iter donate to front nth element
<, >, <=, >= compare

Arrays

An array is a data structure that is similar to the library vector type, but size of a array is fixed, which mean we can not add new elements. Sometimes they offer better run-time performance for specialized applications.

Arrays are a compound type, An array declarator has the form a[d], where a is the name being defined and d is the dimension of the array. the next code show how initial a array:

int p = 10;
int a[10];   //array of ten ints
int* b[10];  //array of ten points to int
int d[p];    //error: p is not a constexpr
int c[] = { 1,2,3 }; //array of three ints, 1 and 2 and 3

When we define an array, we must specify a type for the array. We cannot use auto to deduce the type from a list of initializers. As with vector, arrays hold objects. Thus, there are no arrays of references.

Character arrays have an additional form of initialization. We can initialize such arrays from a string literal. When we use this form of initialization, it is important to remember that string literals end with a null character.

In previous code, we see int* b[10] mean array of ten points to int, so how can we make a pointer point to a array? The next show:

int a[10];   //array of ten ints
int(*p)[10] = &a; //a pointer point to a
int(&r)[10] = a;  //a reference refer to a
//By default, type modifiers bind right to left.

As with the library vector and string types, we can use a range for or the subscript operator to access elements of an array. As usual, the indices start at 0. When we use a variable to subscript an array, we normally should define that variable to have type size_t. size_t is a machine-specific unsigned type that is guaranteed to be large enough to hold the size of any object in memory.

In C++ pointers and arrays are closely intertwined. In particular, as we’ll see, when we use an array, the compiler ordinarily converts the array to a pointer.

#include <iostream>

int main()
{
    int f = 10;
    int a[10];   //array of ten ints
    for (auto &i : a) {
        i = f--;
    }
    //p is a pointer point to the first element of a
    auto p = a;
    //will print 10
    std::cout << *p << std::endl;
    return 0;
}

Array is not a class, so it is not has member donated to begin and end, c++ provide a way to get pointer to fist element and last element. We will use this function in next code.

Many C++ programs predate the standard library and do not use the string and vector types. Moreover, many C++ programs interface to programs written in C or other languages that cannot use the C++ library. Hence, programs written in modern C++ may have to interface to code that uses arrays and/or C-style character strings. The C++ library offers facilities to make the interface easier to manage.

#include <iostream>
#include <string>
#include <vector>
using std::string; using std::vector; using std::begin; using std::end;

int main()
{
    char str[] = "Hello";
    string s(str);  //c string to std string
    const char* cs = s.c_str(); // std string to c string, must need const
    int int_arr[] = { 0, 1, 2, 3, 4, 5 };
    // ivec has six elements; each is a copy of the corresponding element in int_arr
    vector<int> ivec(begin(int_arr), end(int_arr));
    return 0;
}

life is perfact