C++ Primer (Fifth Edition) - 1

1. constexpr variable

        The new C++11 standard stipulates that variables are allowed to be declared as constexpr types so that the compiler can verify whether the value of the variable is a constant expression. A variable declared constexpr must be a constant and must be initialized with a constant expression.

constexpr int mf = 20;// 20 是常量表达式
constexpr int limit = mf + 1; // mf + 1是常量表达式
constexpr int sz = size(); // 只有当size是一个constexpr函数时,才是一个正确的声明语句

In general, if a variable is considered to be a constant expression, declare it as constexpr type.

2. Pointers and constexpr

        It must be made clear that if a pointer is defined in a constexpr declaration, the qualifier constexpr is only valid for the pointer and has nothing to do with the object pointed to by the pointer.

const int *p = nullptr; // p是一个指向整型常量的指针
constexpr int *q = nullptr; // q是一个指向整数的常量指针

        The types of p and q are very different. p is a pointer to a constant, and q is a constant pointer. The key is that constexpr sets the object it defines as the top-level const.

constexpr int *np = nullptr;// np是一个指向整数的常量指针,其值为空
int j = 0;
constexpr int i = 42; // i的类型时整型常量
constexpr const int *p = &i; // p是常量指针,指向整型常量i
constexpr int *p1 = &j;// p1是常量指针,指向整数j

3. Type aliases

        A type alias is a name that is a synonym for a type. There are many benefits to using type aliases. It makes complex type names simple and clear, easy to understand and use, and it also helps programmers to clearly know the real purpose of using the type.

        There are two ways to define type aliases. The traditional way is to use the keyword typedef :

typedef double wages; // wages是double的同义词
typedef wages base, *p; // base是double的同义词,p是double*的同义词

        The new standard specifies a new method, using an alias declaration (alias declaration) to define an alias for a type:

using SI = Sales_item; // SI是Sales_item的同义词

        This method uses the keyword using as the beginning of the alias declaration, followed by the alias and the equal sign. Its function is to specify the name on the left side of the equal sign as the alias of the type on the right side of the equal sign.

4. Pointers, constants, and type aliases

        If a type alias refers to a compound type or constant, using it in a declaration statement can have unintended consequences. For example, the following statement uses the type pstring, which is actually an alias for the type char*:

typedef char *pstring;
const pstring cstr = 0; // cstr是指向char的常量指针
const pstring *ps; // ps是一个指针,它的对象是指向char的常量指针

        const is a modification of a given type, and pstring is actually a pointer to char. Therefore, const pstring is a constant pointer to char, not a pointer to a constant character.

4. auto type specifier

        The new C++11 standard introduces the auto type specifier, which allows the compiler to analyze the type of the expression for us.

auto item = val1 + val2; // item初始化为val1和val2相加的结果

5. Composite types, constants, and auto

        Sometimes the auto type inferred by the compiler is not exactly the same as the type of the initial value, and the compiler will appropriately change the result type to make it more consistent with the initialization rules.

· Using a reference is actually using the referenced object, especially when the reference is used as the initial value, auto is the type of the referenced object;

· auto generally ignores the top-level const, while the bottom-level const is retained.

const int ci = i, &cr = ci;
auto b = ci;  // b是一个整数(ci的顶层const特性被忽略掉)
auto c = cr;  // c是一个整数(cr是ci的别名,ci本身是一个顶层const)
auto d = &i;  // d是一个整型指针
auto e = &ci; // e是一个指向整型常量的指针(对常量对象取地址是一种底层const)

6. decltype type indicator

        Sometimes you run into situations where you want to infer the type of a variable you're defining from the type of an expression, but you don't want to initialize the variable with the value of that expression. In order to meet this requirement, the new C++11 standard introduces a second type specifier decltype, which is used to select and return the data type of the operand. In the process, the compiler analyzes the expression and gets its type without actually computing the expression's value:

decltype (f()) sum = x; //  sum的类型就是函数f的返回类型

        The way decltype handles top-level const and references is slightly different from auto. If the expression used by decltype is a variable, decltype returns the type of the variable (including top-level const and references).

        It should be pointed out that a reference always appears as a synonym for the object it refers to, with the exception of decltype.

7. decltype and reference

        If the expression used by decltype is not a variable, decltype returns the type corresponding to the result of the expression. Some expressions tell decl to return a reference type. Generally speaking, when this happens, it means that the result object of the expression can be used as an lvalue of an assignment statement:

int i = 42, *p = &i, &r = i;
decltype (r + 0) b; // 正确,加法的结果时int,因此b是一个(未初始化的)int
decltype (*p) c; // 错误,c时int&,必须初始化

        Since r is a reference, the result of decltype(r) is a reference type. If you want the result type to be the type pointed to by r, you can use r as part of the expression, such as r+0. Obviously, the result of this expression will be a concrete value rather than a reference.

        On the other hand, if the content of the expression is a dereference operation, decltype will get the reference type. As we are familiar with, dereferencing a pointer can get the object pointed to by the pointer, and can also assign a value to this object. Therefore, the result type of decltype(*p) is int&, not int.

        Another important difference between decltype and auto is that the result type of decltype is closely related to the form of the expression. There is one situation that needs special attention: for the expression used by decltype, if a pair of parentheses are added to the variable name, the obtained The type will be different than without parentheses.

int i;
decltype ((i)) d; // 错误:d是int&,必须初始化
decltype (i) e; // 正确:e是一个(未初始化的)int

Remember: the result of decltype((v)) (note the double brackets) is always a reference, and the result of decltype(v) is only a reference when v itself is a reference.

8. Preprocessor Overview

        A common technique to ensure that header files are included multiple times and still work safely is the preprocessor (preprocessor). It is inherited from the C language by the C++ language. The preprocessor is a program that is executed before compilation and can partially change what we have. written program.
        A preprocessing feature that C++ programs also use is header guards , which rely on preprocessing variables. Preprocessing variables have two states: defined and undefined. The #define directive sets a name as a preprocessing variable, and the other two directives check whether a specified preprocessing variable has been defined; #ifdef is true if and only if the variable is defined; #ifndef is true if and only if the variable True when undefined, once the check result is true, follow-up operations will be performed until the #endif directive is encountered.

Preprocessed variables ignore the rules of the C++ language regarding scope.

Header files should have guards set even if they are not (yet) included in any other header files. The header file protector is very simple, programmers only need to add it habitually, and there is no need to care too much about whether your program needs it or not.

9. The standard library type string

On the one hand, the C++ standard makes detailed regulations on the operations provided by the library types, and on the other hand, it also makes some performance requirements for the implementers of the library. Therefore, the standard library types are efficient enough for general use cases.

        Copy initialization and direct initialization

string s1 = "hihihi'; // 拷贝初始化
string s2 ("ddddd");  // 直接初始化
string s3(10,‘c’);  // 直接初始化

getline(cin, strLine)

The newline character that triggers the return of the getline function is actually discarded, and the obtained string object does not contain the newline character.

auto len = str.size()

If there is already a size() function in an expression, don't use int anymore, so as to avoid the possible problems caused by mixing int and unsigned.

For historical reasons, and for compatibility with C, string literals in the C++ language are not objects of the standard library type string. Remember that a string literal is a different type than string.

Suggestion: use the C++ version of the C standard library headers


        In addition to defining the functions specific to the C++ language, the C++ standard library is also compatible with the standard library of the C language. The header files of C language are in the form of name.h, and C++ names these files as cname, that is, the . Header file for the other side.

        Therefore, the content of the cctype header file is the same as that of the ctype.h header file, but it is more in line with the requirements of the C++ language in terms of naming conventions. In particular, names defined in a header file named cname belong to the namespace std, whereas names defined in a header file named .h do not.

        In general, C++ programs should use header files named cname instead of name.h, and names in the standard library can always be found in the namespace std. If you use header files in the form of .h, programmers have to keep in mind which ones are inherited from the C language and which ones are unique to the C++ language.

The subscript of the string object must be greater than or equal to 0 but less than s.size().

        Using subscripts outside this range will cause unpredictable results, and by extension, using subscripts to access empty strings will also cause unpredictable results.

Tip: Pay attention to check the legitimacy of the subscript

        When using the subscript, you must ensure that it is within a reasonable range, that is, the subscript must be greater than or equal to 0 but less than the value of the size() of the string. A simple and easy method is to always set the type of the subscript to string::size_type, because this type is an unsigned number, which can ensure that the subscript will not be less than 0. At this time, the code only needs to ensure that the subscript is smaller than size() The value is fine.

  • The C++ standard does not require the standard library to check that subscripts are valid. Once an out-of-range subscript is used, unpredictable results can occur.

10. The standard library type vector

        The C++ language has both class templates and function templates, among which vector is a class template.

        A template itself is not a class or function, but instead a template can be thought of as an instruction for the compiler to generate a class or function. The process by which the compiler creates a class or function based on a template is called instantiation . When using a template, it is necessary to propose what type the compiler should instantiate the class or function into.

        Vector is a template rather than a type. The type generated by vector must contain the type of the elements in vector, such as vector<int>.

        Some compilers may still need to handle vector objects with unvectored elements in the old-fashioned Shengming fishing gear, such as vector<vector<int>>

Key concept: vector objects can grow efficiently

        The C++ standard requires that vectors should be able to add elements efficiently and quickly at runtime. So since the vector object can grow efficiently, there is no need to set its size when defining the vector object, and in fact, the performance may be worse if it is done so. There is only one exception, when all elements have the same value. Once the values ​​of the elements are different, it is more efficient to define an empty vector object and add specific values ​​to it at runtime. In addition, vector also provides methods that allow us to further improve the performance of dynamically adding elements.

        Creating an empty vector object initially and adding elements dynamically at runtime differs from the usage of built-in array types in C and most other languages. Especially if you are used to C or Java, you can expect that it is best to specify its capacity when creating a vector object, but in fact, the opposite is usually the case.

  • The body of a range for statement should not change the size of the sequence it iterates over.
  • To use size_type, you need to specify which type it is defined by. The type of the vector object always contains the type of the elements. (vector<int>::size_type)
  • The subscript operator on vector objects (and string objects) can be used to access existing elements, not to add elements.

11. Using iterators

        Unlike pointers, obtaining iterators does not use address-taking symbols, and types with iterators also have members that return iterators. For example, these types have members named begin and end, where the begin member is responsible for returning an iterator pointing to the first element (or first character). The following statement:

auto b = v.begin(), e = v.end();

        The end member is responsible for returning an iterator pointing to the "next position of the tail element (one past the end)" of the container (or string object), that is to say, the iterator indicates a non-existent "behind the end" of the container. (off the end)" element.

If the container is empty, begin and end return the same iterator, both after the end iterator.

Operators for standard container iterators
*iter Returns a reference to the element pointed to by the iterator iter
iter->mem Dereference iter and get the element's member named mem, equivalent to (*iter).mem
++iter Let iter indicate the next element in the container
--iter Let iter indicate the previous element in the container
iter1 == iter2 Checks if two iterators are equal
iter1 != iter2 Checks if two iterators are not equal

Because the iterator returned by end does not actually refer to an element, it cannot be incremented or dereferenced.

Key Concepts: Generic Programming

        It is a bit strange for programmers who originally used C or Java to use != instead of < to judge for loop after switching to C++ language, such as the above program. C++ programmers use != habitually for the same reason they prefer iterators to subscripts; because this style of programming works on all containers provided by the standard library.

        Only some standard library types like string and vector have subscript operators, not all of them. Similarly, the iterators of all standard library containers define the habits of == and !=, so you don't have to care too much about which container type you use.

Terminology: Iterators and Iterator Types

        The term iterator has three different meanings: it may be the iterator concept itself, it may refer to the iterator type defined by the container, and it may refer to an iterator object.

        The point is to understand that there is a set of conceptually related types that we consider a type to be an iterator if and only if it supports a set of operations that allow us to access the elements of a container or move from one element to another .

        Each container class defines a type called iterator that supports the set of operations specified by the iterator concept.

Remember, any loop body that uses an iterator should not add elements to the container to which the iterator belongs.

Operations supported by vector and string iterators
iter + n An iterator plus an integer value is still an iterator, moved backward n elements
iter-n move n elements forward
iter1 += n A compound assignment statement for iterator addition, assigning the result of iter1 plus n to iter1
iter1 -= n  A compound assignment statement for iterator subtraction, assigning the result of subtracting n from iter1 to iter1
iter1 - iter2 The result of subtracting two iterators is the distance between them
> >= < <= Relational Operators for Iterators

12 array

        If the exact number of elements is not known, use vector.

        Arrays are a compound type. The declaration of an array goes into a[d], where a is the name of the array and d is the dimension of the array. The dimension specifies the number of elements in the array, so must be greater than 0,. The number of elements in the array is also part of the array type, and the dimensions should be known at compile time. That is, dimension must be a constant expression .

unsigned cnt = 42;         // 不是常量表达式
constexpr unsigned sz = 42;// 常量表达式
int arr[10];               // 含有10个整数的数组
int *parr[sz];             // 含有42个整型指针的数组
string bad[int];           // 错误,cnt不是常量表达式
string strs[get_size()];   // 当get_size()是constexpr时正确,否则错误

By default, the dark elements of the array are initialized by default.

As with variables of built-in types, if an array of a built-in type is defined inside a function, default initialization will cause the array to contain undefined values.

        When defining an array, the type of the array must be specified, and the auto keyword is not allowed to infer the type from the list of initial values. In addition, like vector, the elements of the array should be objects, so there is no referenced array.

        Display initialization array elements: the dimension of the array must be greater than or equal to the total number of initial values.

        The particularity of the character array: there is a null character at the end of the string itself, and this null character will be copied to the character array like other characters in the string.

        Copying and assignment are not allowed: the contents of an array cannot be copied to other arrays as its initial value, nor can an array be used to assign values ​​to other arrays.

int a[] = {};// 含有3个整数的数组
int a2[] = a;// 错误,不允许使用一个数组初始化另一个数组
a2 = a;// 错误,不能把一个数组直接赋值给另一个数组

        Some compilers support array assignment, which is called a compiler extension . But in general, it is best to avoid using non-standard features, because programs containing non-standard features are likely to fail correctly on other compilers.

work regularly.

Understand complex array declarations

        Like vectors, arrays can hold most types of objects. For example, you can define an array that stores pointers, and because the array itself is an object, it is allowed to define pointers to arrays and references to arrays. In these cases, defining an array of pointers is relatively simple and straightforward, but defining a pointer to an array or a reference to an array is a little more complicated.

int *ptrs[10];            // ptrs是含有10个整数指针的数组
int &refs[10] = /* ? */;  // 错误,不存在引用的数组
int (*Parray)[10] = &arr; // Parray指向一个含有10个整数的数组
int (&arrRef)[10] = arr;  // arrRef引用一个含有10个整数的数组

The best way to understand the meaning of an array declaration is to read it from the inside out, starting with the name of the array.

Check the value of the subscript

        Most common security problems stem from buffer overflow bugs. This type of error occurs when an array or other type of data structure has a subscript out of bounds and attempts to access an illegal memory area.

pointers and arrays

        In most expressions, using an object of type array actually uses a pointer to the first element of the array.

Standard library functions begin and end

        Although the trailing pointer can be computed, this usage is extremely error-prone. In order to make the use of pointers easier and safer, the new C++11 standard introduces two functions named begin and end. These two functions are similar to the two members of the same name in the container, but the array is not a class type after all, so these two functions are not member functions.

int ia[] = {0,1,2,3,4,5,6,7,8,9};// ia是一个含有10个整数的数组
int *beg = begin(ia); // 指向ia首元素的指针
int *end = end(ia); // 指向ia尾元素的下一位置的指针

// 寻找第一个负值元素,如果已经检查完全部元素则结束循环
while(beg != end && *beg >= 0)
{
    ++beg;
}

        If a pointer points to the "next position" of the end element of an array of a built-in type, it has a function similar to that of an iterator returned by the end function of vector. In particular, note that pointers after the tail cannot be dereferenced and incremented.

        The index value used by the built-in subscript operator is not an unsigned type, which is different from vector and string.

int ia[] = {0,1,2,3,4};
int *p = &ia[2]; // p指向索引为2的元素
int j = p[1];// p[1]等价于 *(p+1),就是ia[3]表示的那个元素
int k = p[-2]; // p[-2]是ia[0]表示的那个元素

C-style string

        Although C++ supports C strings, it is best not to use them in C++ programs. This is because C-style strings are not only inconvenient to use, but also easily lead to program vulnerabilities, which is the root cause of many security problems.

String literals are an example of a general-purpose construct that is C - style strings         that C++ inherited from C. C-style strings are not a type, but a conventional way of expressing and using strings. Conventional strings are stored in character arrays and are terminated by a null character . Null-terminated means that the last character of the string is followed by a null character ('\0'). Generally, pointers are used to manipulate these strings.

C standard library String function

C-style string functions
strlen(p) Returns the length of p, null characters are not counted
strcmp(p1,p2)

Compare p1 and p2: equal, return 0; greater than, return positive; otherwise, negative

strcat(p1,p2) After appending p2 to p1, return p1
strcpy(p1,p2) Copy p2 to p1 and return p1
The above functions are not responsible for validating their string arguments.

For most applications, using standard library strings is safer and more efficient than using C-style strings.

Mixing string objects and C-style strings

  • Allows initializing a string object with a null-terminated character array as a string object assignment.
  • In the addition operation of string objects, it is allowed to use a character array terminated by a null character as one of the operations exclusive (not both operands); in the compound assignment operation of a string object, it is allowed to use a character array terminated by a null character as The right operand.

If the program wants to use the array returned by the .c_str() function all the time, it is better to copy the array again.

Initialize the vector object with an array

int int_arr[] = {0,1,2,4,5,6,6,6,6};
vector<int> ivec(begin(int_arr),end(int_arr));
vector<int> subvec(int_arr + 1, int_arr + 4);

Recommendation: Try to use standard library types instead of arrays

        It's easy to make mistakes with pointers and arrays. Part of the reason is a conceptual problem: pointers are commonly used in low-level operations, so they are prone to errors related to tedious details. Other problems stem from syntax errors, especially when declaring pointers.

        Modern C++ programs should try to use vectors and iterators instead of vector arrays and pointers; try to use strings instead of C-style array-based strings.

Working with multidimensional arrays using the range for statement

  •         Due to the addition of the range for statement in the new C++11 standard, array access can be simplified to the following form
size_t cnt = 0;
for(auto &row : ia)
{
    for(auto &col : row)
    {
        col = cnt;
        cnt++'
    }
}

        To use a range for statement with multidimensional arrays, the control variables of all but the innermost loops should be reference types .

Glossary 

  • begin is a member of string and vector, and returns an iterator pointing to the first element. It is also a standard library function that inputs an array and returns a pointer to the first element of the array.
  • Buffer overflow (buffer overflow) is a serious program failure. The main reason is trying to access the contents of a container through an out-of-bounds index. The container types include string, vector, and array.
  • A C-style string is a null-terminated character array. String literals are C-style strings, and C-style strings are error-prone.
  • Class templates are used to create templates for concrete class types. To use a class template, auxiliary information about the type must be provided. For example, to define a vector object you need to specify the type of elements: vector<int> contains elements of type int.
  • Compiler extension (complier extension) A specific compiler adds additional features to the C++ language. Programs written based on compiler extensions are not easily portable to other compilers.
  • A container is a type whose objects hold a set of objects of a given type. vector is a container type.
  • Copy initialization (copy initialization) uses the initialization form of the assignment sign (=). The newly created object is a copy of the initial value.
  • difference_type is a signed integer type defined by string and vector, representing the distance between two iterators.
  • Direct initialization (direct initialization) does not use the assignment sign (=) initialization form.
  • empty is a member of string and vector and returns a boolean value. Returns true when the size of the object is 0, false otherwise.
  • end is a member of string and vector, and returns an iterator after the end. Also a standard library function that takes an array as input and returns a pointer to the next position of the last element of the array.
  • getline is a function defined in the string header file, which takes an istream object and a string object as input parameters. This function first reads the content of the input stream until it encounters a newline character, then stores the read data into the string object, and finally returns the istream object. where newline characters are read but not preserved.
  • Index (index) is the value used by the subscript operator, indicating a position to be accessed in a string object, vector object, or array.
  • Instantiation is the process by which the compiler generates a specified template class or function.
  • An iterator is a type used to access or move between elements in a container.
  • Iterator arithmetic (iterator arithmetic) is the operation of the iterator of string or vector: the iterator and the integer are added and subtracted to get a new iterator, compared with the original iterator, the new iterator has moved forward or backward by a certain amount locations. Two iterators are subtracted to get the distance between them. At this time, they must point to the same container element or the next position of the container tail element.
  • A null-terminated string is a string whose last character is followed by a null character ('\0').
  • off-the-end iterator The iterator returned by the end function. Points to an element that does not exist, and the calcium element is located one position after the end of the container.
  • Pointer arithmetic is an arithmetic operation supported by pointer types. Pointers to arrays support the same kinds of operations as iterator operations.
  • ptrdiff_t is a signed integer type related to machine implementation defined in the cstddef header file. Its space is large enough to represent the distance between any two pointers in the array.
  • push_back is a member of vector, adding elements to the end of the vector object.
  • A range for statement (range for) is a control statement that can iterate over a specific set of values.
  • size is a member of string and vector, and returns the number of characters or the number of elements, respectively. The type of the return value is size_type.
  • size_t is an unsigned integer type defined in the cstddef header file that is related to machine implementation. Its space is large enough to represent the size of any array.
  • size_type is the name of the type defined by string and vector, which can store the size of any string object or vector object. In the standard library, size_type is specified as an unsigned type.
  • string is a standard library type that represents a sequence of characters.
  • A using declaration (using declaration) makes a name in the namespace available to the program directly.
  • Value initialization (value initialization) is an initialization process. Built-in types are initialized to 0 and class types are initialized by the default constructor of the class. Only when the class contains a default constructor, the object of the class will be value-initialized. For the initialization of the container, if only the size of the container is specified without specifying the initial value, value-initialization will be performed. At this point the compiler generates a value, and the elements of the container are initialized to that value.
  • A vector is a standard library type that holds a set of elements of a specified type.
  • The ++ operator (++operator) is an increment operator defined by iterators and pointers, and performs the "plus" operation to make the iterator point to the next element.
  • [] operator ([] operator) subscript operator. obj[i] gets the element at position j in the container object obj. The index starts from 0, the index of the first element is 0, and the index of the last element is obj.size()-1. The return value of the subscript operator is an object. If p is a pointer and n is an integer, then p[n] is equivalent to *(p+n).
  • ->operator (->operator) Arrow operator, this operator understands reference operation and dot operation comprehensively. a->b is equivalent to (*a).b.
  • The << operator (<<operator) is an output operator defined by the standard library type string. Responsible for outputting the characters in the string object.
  • >>operator (>>operator) is an input operator defined by the standard library type string. It is responsible for reading a set of characters, and stops when encountering a blank. The read content is assigned to the operand on the right side of the operator. The operator should is a string object.
  • The ! operator (!operator) is a logical NOT operator that inverts the Boolean value of its operand. If the operand is false, the result is true, and if the operand is true, the result is false.
  • The && operator (&&operator) arrow logical AND operator, if both operands are true, the result is true. The right operand is checked only if the left operand is true.
  • || operator (||operator) Arrow logical or operator, any operand is true, the result is true. The right operand is checked only if the left operand is false.

Guess you like

Origin blog.csdn.net/liangfei868/article/details/126618475