#include #include "complex.h"
using namespace std;
ostream& operator << (ostream& os, const complex& x) { return os << '(' << real (x) << ',' << imag (x) << ')'; }
int main() { complex c1(2, 1); complex c2(4, 0);
cout << c1 << endl; cout << c2 << endl;
cout << c1+c2 << endl; cout << c1-c2 << endl; cout << c1*c2 << endl; cout << c1 / 2 << endl;
cout << conj(c1) << endl; cout << norm(c1) << endl; cout << polar(10,4) << endl;
cout << (c1 += c2) << endl;
cout << (c1 == c2) << endl; cout << (c1 != c2) << endl; cout << +c2 << endl; cout << -c2 << endl;
cout << (c2 - 2) << endl; cout << (5 + c2) << endl;
return 0; }
As shown in the figure above, self-assignment needs to be checked when overloading the "=" assignment operator, for the following reasons:
If there is no self-assignment detection, then the m_data of the own object will be released, and the content pointed to by m_data will not exist, so there will be problems with the copy.
Here are two classes for explanation:
class complex
{
public:
complex (double r = 0, double i = 0)
: re (r), im (i)
{ }
complex& operator += (const complex&);
double real () const { return re; }
double imag () const { return im; }
private:
double re, im;
friend complex& __doapl (complex*,
const complex&);
};
class String
{
public:
String(const char* cstr = 0);
String(const String& str);
String& operator=(const String& str);
~String();
char* get_c_str() const { return m_data; }
private:
char* m_data;
};
After creating these two objects, the compiler (VC) allocates memory to the two objects as follows:
The two on the left are the compiler memory allocation of the class complex in debug mode and release mode. In debug mode, the compiler inserts the head and tail (red part), 4*8 + 4 size information part (gray part) into the complex object memory. The green part is the space actually occupied by the complex object. After calculation, there are only 52 words. section, but the VC is aligned to 16 bytes, so the nearest 16 multiple of 52 is 64, and the 12-byte gap should be filled (cyan pad part). For the complex object in the release part, only the information header and the header part are added. The analysis of the string class is basically the same.
Next, let’s look at how the VC compiler allocates array objects:
Similarly, the compiler adds some redundant information to the object. For complex class objects, since the array has three objects, there are 8 doubles. Then the compiler inserts "3" in front of the three complex objects to mark the individual objects. number. The analysis method of String class is also similar.
The picture below illustrates why deleting an array object requires the delete[] method:
String.h
#ifndef __MYSTRING__
#define __MYSTRING__
class String
{
public:
String(const char* cstr=0);
String(const String& str);
String& operator=(const String& str);
~String();
char* get_c_str() const { return m_data; }
private:
char* m_data;
};
#include <cstring>
inline
String::String(const char* cstr)
{
if (cstr) {
m_data = new char[strlen(cstr)+1];
strcpy(m_data, cstr);
}
else {
m_data = new char[1];
*m_data = ' ';
}
}
inline
String::~String()
{
delete[] m_data;
}
inline
String& String::operator=(const String& str)
{
if (this == &str)
return *this;
delete[] m_data;
m_data = new char[ strlen(str.m_data) + 1 ];
strcpy(m_data, str.m_data);
return *this;
}
inline
String::String(const String& str)
{
m_data = new char[ strlen(str.m_data) + 1 ];
strcpy(m_data, str.m_data);
}
#include <iostream>
using namespace std;
ostream& operator<<(ostream& os, const String& str)
{
os << str.get_c_str();
return os;
}
#endif
string_test.cpp
#include "string.h"
#include <iostream>
using namespace std;
int main()
{
String s1("hello");
String s2("world");
String s3(s2);
cout << s3 << endl;
s3 = s1;
cout << s3 << endl;
cout << s2 << endl;
cout << s1 << endl;
}
In C++, a one-parameter constructor (or a multi-parameter constructor with default values for all parameters except the first parameter) plays two roles. 1 is a constructor, 2 is a default and implicit type conversion operator.
Therefore, sometimes when we write code such as AAA = XXX, and the type of XXX happens to be the parameter type of the AAA single-parameter constructor, the compiler will automatically call this constructor to create an AAA object.
This looks cool and very convenient. But in some cases (see the authoritative example below), it goes against our (programmers') original intentions. At this time, you need to add explicit modification in front of the constructor to specify that this constructor can only be called/used explicitly and cannot be used implicitly as a type conversion operator.
The explicit constructor is used to prevent implicit conversions. Please look at the code below:
class Test1
{
public:
Test1(int n)
{
num=n;
}//普通构造函数
private:
int num;
};
class Test2
{
public:
explicit Test2(int n)
{
num=n;
}//explicit(显式)构造函数
private:
int num;
};
int main()
{
Test1 t1=12;//隐式调用其构造函数,成功
Test2 t2=12;//编译错误,不能隐式调用其构造函数
Test2 t2(12);//显式调用成功
return 0;
}
The constructor of Test1 takes an int parameter, and line 23 of the code will be implicitly converted to calling the constructor of Test1. The constructor of Test2 is declared as explicit, which means that the constructor cannot be called through implicit conversion, so a compilation error will occur on line 24 of the code.
Ordinary constructors can be called implicitly. The explicit constructor can only be called explicitly.
When any Fraction needs to be converted to double type, the double() function is automatically called for conversion. As shown in the figure above, the compiler determines that 4 is an integer during the analysis of double d = 4 + f, and then continues to determine f. It is observed that f provides the double() function, and then performs the double() operation on f, and calculates 0.6 , then add it to 4, and finally get the double type 4.6.
A class is defined in the figure above, called Fraction. The "+" operator is overloaded in the class. During the f+4 operation, "4" is implicitly converted (constructor) by the compiler into a Fraction object, and then passed through Fraction The overloaded "+" operator participates in the operation.
As shown in the figure above, the double() function is added to Fraction, which divides the two member variables of Fraction, and then forcibly converts them to double type and returns the result. During the f+4 overloading process, the compiler will report an error. You can Make the following analysis:
1. First, 4 is implicitly converted (constructor) into a Fraction object, and then the overloaded "+" operator is operated with "f" to return a Fraction object;
2. First, 4 is implicitly converted (constructor) into a Fraction object, and then a double operation is performed on the pair through the overloaded "+" operator and "f" operation, and finally a Fraction object is returned;
3. . .
So the compiler has at least two ways to go, which creates ambiguity and reports an error. As shown in the figure above, if the explict keyword is added before the constructor Franction, the implicit conversion will be cancelled. Therefore, during the execution of d2 = f + 4, f will call the double function to convert to 0.6, and then add it to 4 to become 4.6. Since the constructor cancels implicit formula conversion, 4.6 cannot be converted to Fraction, so an error will be reported.
The following figure shows an application of operator overloading and conversion functions in C++ stl:
The picture below illustrates the internal structure and usage of smart pointers very well: There are three key points in the syntax of smart pointers. The first is the saved external pointer, which corresponds to T* px in the picture above. This pointer The operations related to the incoming pointer will be performed instead of the incoming pointer; the second is to overload the "*" operator, dereference, and return an object pointed to by the pointer; the third is to overload the "->" operator, to return A pointer corresponding to the picture above is px.
Iterators are also a type of smart pointers. There are also the three elements of smart pointers mentioned above, which correspond to the red font and yellow marked parts in the figure below:
The "*" and "->" overloading characters of iterator overloading will be carefully analyzed below:
Create a list iterator object, list::iterator ite; the list here is used to save the Foo object, which is the class in the list template definition T, operator*() returns a (*node).data object, node is of __link_type type, but __link_type is of __list_node<T>* type. T here is Foo, so node is __list_node<Foo >* type, so (*node).data gets an object of type Foo, and &(operator*()) finally gets an address of the Foo object, that is, returns Foo* A pointer to the type.
As you can see from the above figure, each functor is a class that overloads the "()" operator, and then becomes a "functor". It is actually a class, but it appears to have the properties of a function. Each functor actually integrates a strange class behind it, as shown in the figure below. This class does not need to be explicitly declared manually by the programmer. The functor in the standard library also inherits a strange class: The content of this class is shown in the figure below. It just declares some things, and there are no actual variables or functions in it. The specific content will be discussed in STL.
Different from class templates, function templates do not need to explicitly declare the type of the incoming parameters when using them, the compiler will automatically deduce the type.
Member templates are often used in generic programming. In order to have better scalability, take the above figure as an example. T1 is often the base class of U1, and T2 is often the base class of U2. You can see the following example: Through this In this way, as long as the parent or ancestor classes of U1 and U2 passed in are T1 and T2, inheritance and polymorphism can be cleverly utilized through this method, but not vice versa. This method is used a lot in STL:
As the name suggests, template partialization refers to specifying specific data types in the template, which is different from generalization: Of course, template partialization also has degrees, and partial types can be specified, which is called partial specialization:
Too much content will be explained in the C++11 course, so I will only introduce it here for now.
Too much content will be explained in the C++11 course. I will only introduce it here for now.
A reference can be seen as an alias for a referenced variable.
As shown in the figure above, three classes are defined, A, B and C. B inherits from A, and C inherits from B. There are two virtual functions in A, one in B, and one in C. The compiler allocates the object a of A in the memory as shown in the figure above. There are only two member variables m_data1 and m_data2. At the same time, since class A has virtual functions, the compiler will allocate a space to the object a to save the virtual functions. table, this table maintains the virtual function address (dynamic function) of this class Stateful binding), since class A has two virtual functions, there are two spaces (yellow and blue spaces) in the virtual function table of a pointing to A::vfunc1() and A::vfunc2() respectively; similarly, b It is an object of class B, because class B overrides the vfunc1() function of class A. , so B's virtual function table (cyan part) will point to B::vfunc1(), and B inherits vfunc2() of class A, so B's virtual function table (blue part) will point to A of parent class A: :vfunc2() function; similarly, c is an object of class C, represented by Class C rewrites the vfunc1() function of the parent class, so C's virtual function table (yellow part) will point to C::vfunc1(). At the same time, C inherits vfunc2() of superclass A, so B's virtual function The table (blue part) will point to the A::vfunc2() function. At the same time, the above figure also uses C language code to illustrate how the bottom layer of the compiler calls these functions. This is the essence of object-oriented inheritance polymorphism.
The this pointer can actually be considered as a pointer pointing to the memory address of the current object. As shown in the figure above, since there are virtual functions in the base class and subclass, this->Serialize() will be dynamically bound, which is equivalent to (*(this- >vptr)[n])(this). It can be understood by combining the virtual pointer and virtual function table in the previous section. As for why it is correct to write like this in the end, the following summary will explain.