"C + + programming principles and Practice" reading notes (v)

Source: Internet
Author: User
Tags arithmetic


Copy


Our vector type has the following form:

Class vector{Private:int sz; Double * Elem;public:vector (int s): SZ (s), Elem (new Double[s]) {} ~vector () {delete [] elem;};


Let's try to copy one of these vectors:

void f (int n) {vector V (3);    V.set (2, 2.2);    Vector v2 = v; //...}

For one type, the default meaning of the copy is "Copy all data members". For object V and v2, because the pointer elem points to the same piece of memory, it would have disastrous consequences to repeat two times to release the memory. So, what should we do? We need to display the copy operation: When initializing another vector object with one vector object, all vector elements should be copied and the copy operation is guaranteed to be called. The initialization of an object of a certain type is implemented by the constructor of that type. So, to implement a copy operation, we need to implement a specific type of constructor. This type of constructor is called a copy constructor. The parameters of the C + + definition copy constructor should be a reference to the copied object. Thus, for a type vector, its copy constructor is as follows: vector (const vector&); This copy constructor will be called when we attempt to initialize another vector object using one of the vector objects. The reason that a copy constructor uses an object reference as a parameter is that we do not want to have a copy of the parameter while passing the function arguments, but the reason for using the const reference is that we do not want the function to modify the parameters. Therefore, we redefine the vector type as follows:

class vector{int sz;         Double *elem;    void copy (const vector& arg) {for (int i = 0; i < ARG.SZ; ++i) elem[i] = Arg.elem[i];}        Public:vector (const vector&): SZ (ARG.SZ), Elem (new Double[arg.sz]) {copy (ARG); //...};

Copy Assignment


We can copy (initialize) the object through the constructor, but we can also copy the vector object by assigning a value. Similar to copy initialization, copy assignment defaults to a copy of the object's members. Therefore, for the vector types we currently define, copying assignments can cause problems such as duplicate data deletion and memory leaks. For example:

void F2 (int n) {vector V (3);     V.set (2, 2.2);     Vector v2 (4);     v2 = V; // ...}

We want the object v2 to be a copy of Object V (the vector type of the standard library is implemented this way, but because we do not define a copy assignment operation for the vector type, the default copy assignment operation is performed, that is, the assignment will be a member copy). We should define the copy assignment operation as follows:

class vector{int sz;     Double * ELEM; void copy (const vector& arg);p ublic:vector& operator= (const vector&);
vector& vector::operator= (const vector& a) {double * p = new DOUBLE[A.SZ];     for (int i = 0;i < A.sz; ++i) p[i] = A.elem[i];     delete [] elem;     Elem = p;     SZ = A.SZ; return * this;}

Copy assignment operations are slightly more complex than copy construction operations because the copy assignment operation requires consideration of the processing of an object's original elements.


When implementing a copy assignment operation, we can simplify the code by releasing the memory used by the original element before creating the copy, but this is not a good practice. Better yet, we should keep the original elements until we are sure that the original elements can be safely released. If we do not do this, then assigning an object to itself will have the potential to produce strange results.


Copy terminology


For most programs and programming languages, copying poses a lot of problems. A fundamental question is whether you should copy a pointer (or reference) or copy the data that the pointer points to (or refer to):

(1) A shallow copy copies only pointers, so two pointers may point to the same object.

(2) A deep copy will copy the data pointed to by the pointer, so two pointers will point to two different objects. When we need to implement a deep copy of an object of a certain type, we need to explicitly define our own copy constructor and copy assignment function for that type.

Types that implement shallow copies, such as pointers and references, are referred to as having pointer semantics or referential semantics (they copy addresses). A type that implements a deep copy is known as having value semantics (the value to which they copy points). From the user's point of view, a copy operation with a value semantic type does not involve a pointer-only the value is copied. It can also be said that when copying, a type with value semantics behaves as if it were an integer type.


Necessary actions


A type should choose which constructors, whether the type should define a destructor, whether the type should define a copy assignment function.

(1) A constructor function with one or more parameters;

(2) default constructor;

(3) Copy constructors (copy objects of the same type);

(4) Copy Assignment function (copy object of the same type);

(5) destructor

In general, we need one or more constructors to implement the object's initialization with different parameters. The meaning/purpose of the actual start value depends entirely on the constructor. Typically, we use constructors to create invariant expressions. If we cannot define a good invariant that the constructors of a class can establish, then it is likely that we have used a bad class design or a simple data structure. The form of constructors with parameters differs depending on the type in which they are located. In contrast, other operations have a more regular form. How do we know if a type needs a default constructor? If we want to construct an object of that type without specifying an initial value, the type requires a default constructor. If a type needs to obtain system resources, the type requires a destructor. Another characteristic of a type that requires a destructor is that the type has a pointer member or a reference member. If a type has pointer or reference members, the type typically requires the implementation of destructors and copy operations. Typically, a type that implements a destructor also needs to implement a copy constructor and a copy assignment function. The reason is simple: if an object of that type gets a resource (or has a pointer to a resource), then only the default copy (shallow copy) will almost certainly bring an error. Also, for a base class, if its derived class has a destructor, the destructor for that base class should be a virtual function.


Display constructor: A constructor that has only one parameter defines a conversion from its argument type to the type secret History the function. This kind of conversion is very important. For example:

Class complex{Public:complex (double);         Complex (double, double); //...}; Complex Z1 = 3.14;complex z2 = Complex (1.2, 3.4);


However, we should be cautious about using implicit conversions, because implicit conversions can have unpredictable consequences. Fortunately, we are able to disallow the use of constructors for implicit conversions of types in a simple way. Constructors that are modified by the keyword explicit (that is, explicit constructors) can only be used for object construction and cannot be used for implicit conversions. For example:

Class vector{//... explicit vector (int); // ...};  Vector v = 10;   Errorv = 20;   Errorvector V0 (10); Ok


Debugging constructors and destructors


During the execution of the program, both the constructor and the destructor are called at a definite, predictable point in time. However, we do not always need to invoke these functions in an explicit manner. These functions are also called when we are doing something. The invocation of constructors and destructors can cause confusion in the syntax of people. The following are the occasions where the common constructors and destructors are called:

(1) Whenever an object of type X is constructed, a constructor of type x is called.

(2) Whenever an object of type X is destroyed, the destructor of type X is called.

Whenever an object of type is destroyed, the destructor for that type is called, which can occur at the end of the scope of the variable, at the end of the program, or when delete acts on a pointer to an object. Whenever an object of the type is constructed, the constructor of that type is called, which can occur when the variable is initialized, when the object is built with new (in addition to the built-in type) and when the object is copied. In order to understand this problem, we added a print statement to the constructor, the assignment function, and the destructor.

Struct x{     int val; void out (Const string&s, int &NBSP;NV)  { cerr << this <<   << s < <  ":"  <<  "("  << nv <<  ") \ n";}  x ()  { out ("X ()",  0);  val = 0;}  x (INT&NBSP;V)  {out ("X (int)",  v);  val = v;}  x (const x& x)  {out ("X (x&)",  x.val);  val = x.val;}  x& operator (Const x& a)  {out ("x::operator= (),  a.val);  val =  a.val; return * this;}  ~x ()  {out ("~x ()",  0);}; X glob (2); X copy (X a)  {return a;} X copy2 (X a)  { x aa = a; return aa;} X& ref_to (X& a)  {return a;} X* make (int i)  {x a (i);  return new x (a);}x* make (int i)  {x a (i);  return new x (a); struct xx {x a; x b;}; Int main () {     x loc (4);      x loc2 =  loc;     loc = x (5);     loc2 =  Copy (loc);      loc2 = copy2 (Loc); &NBSP;&NBSP;&NBSP;&NBSP;&NBSP;X&NBSP;LOC3 ( 6);      x& r =ref_to (Loc);     delete  Make (7);      delete make (8);     vector <x>  v (4);      xx loc4;     x * p =new  x (9);      delete p;     x* p = new  x (9);     delete p;     x* pp =  New&nbsP X[5];&NBSP;&NBSP;&NBSP;&NBSP;&NBSP;DELETE&NBSP;[]&NBSP;PP;}


Accessing vector elements

Class vector{double& operator[] (int n) {return elem[n];} Double operator[] (int n) const; Overload operator for const object};

The above implementation causes the subscript operator of the object vector to have a meaning similar to the regular subscript operator: V[i] is interpreted as a function call v.operator[] (i), and the call returns a reference to the element with the number I of Object v.


Array


We have used arrays to refer to objects that are ordered in the free storage area. As with named variables, we can also allocate arrays in other places. In fact, arrays can be used as:

(1) Global variables (but defining global variables is usually a bad idea)

(2) Local variables (but the arrays are strictly limited as local variables)

(3) function member (but an array does not know its size)

(4) A member of a class (but an array member is difficult to initialize)

Now, you may find that we are more in favor of using vector types rather than arrays. We should replace the array with the vector type as much as we can. Even so, the array has been around for a long time before the vector object appeared, and it is much the same as the array of other programming languages (such as the C language), so we must learn how to use arrays so that we can work with code that was written long ago, or code that is written by someone who cannot use the vector type.


An array is a collection of homogeneous objects in a sequential order in memory space-that is, all elements of an array have the same type, and there is no memory gap between the elements. The elements in the array are numbered sequentially, starting with 0. Arrays can be represented by "square brackets":

const int max = 100;int gai[max];void f (int n) {char lac[20];    int lai[60];    Double Lad[n]; // ...}

Note that there is a limit to the use of arrays: for a named array, you must know the number of elements that the array contains when you compile the program. If you want the number of elements to be a variable, then you must allocate the array in free storage and access it via the pointer array. This is what the vector type does. Like arrays stored in free storage, we access named arrays with subscript and dereference operators ([] and *).


Pointer to array element

Double ad[10];d ouble * p = &ad[5];


We can use a positive or negative number as the subscript operand of the pointer. This is true as long as the element is within the range of the array. However, it is illegal to access data that is located outside the array through pointers. Typically, the compiler cannot monitor access to data outside the array range, and such access is likely to be catastrophic. When the pointer points to an array, the plus and subscript operations can change the pointer so that the pointer points to other elements in the array. For example:

p+=2; p-=5;

by operator + 、-、 + =,-= Move pointer can only be moved within the range of the array. Unfortunately, errors caused by pointer arithmetic are sometimes difficult to find. Usually the best strategy is to avoid using pointer arithmetic as much as possible. The most common operation for pointer arithmetic is to increment the pointer (using + +) so that the pointer points to the next element, and the pointer is self-decrement (using--) so that the pointer points to the previous element. For example, we can print ad element values in the following ways:

for (double * p = &ad[0]; p < &ad[10]; ++p) cout << *p << ' \ n ';

or reverse print:

for (double * p = &ad[9]; p >= &ad[0];--p) cout << *p << ' \ n ';

Note that another common way of pointer elements is to pass pointers as arguments to functions. C + + allows pointers to be operated primarily for historical reasons. Part of the reason is that in some low-level applications, using pointer arithmetic is more convenient.


The name of the array represents all the elements of the array. Example: Char ch[100]; The size of CH sizeof (CH) is 100. However, the name of the array can be transformed (degraded) into pointers. For example: char * p = ch;


Palindrome


Use a string to implement a palindrome. Track the progress of character comparisons using the string type of the standard library and an index of type int:

BOOL Is_palindrome (const string& s) {int first = 0;     int last = S.length ()-1;         while (first < last) {if (S[FISRT]! = S[last] return false;         + + first;     --last; } return true;


Using arrays to implement Palindrome

BOOL Is_palindrome (const char s[], int n) {int first = 0;    int last = n-1;       while (first < last) {if (s[fist]! = S[last] return false;       ++first;    --last; } return true;


Using pointers to implement Palindrome

BOOL Is_palindrome (const char * first, const char * last) {When (first < last) {if (*first! = *last) Retu        RN false;        ++first;   --last; } return true;


Note that we can actually do a self-increment or decrement operation on the pointer. The auto-increment action points the pointer to the next element in the array, and the decrement action points the pointer to the previous element. If the area pointed to by the pointer exceeds the actual range of the array, a serious out-of-bounds error will occur. This is an issue that may arise when using pointers.

"C + + programming principles and Practice" reading notes (v)

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.