#include #include "complex.h"
использование пространства имен std;
ostream& оператор << (ostream& os, const complex& x) { return os << '(' << real (x) << ',' << imag (x) << ')' };
int main() { комплекс c1(2, 1); комплекс c2(4, 0);
cout << c1 << endl cout << c2 << endl;
cout << c1+c2 << endl << c1-c2 << endl; cout << c1*c2 << cout << c1 / 2 << endl;
cout << conj(c1) << endl; cout << норма (c1) << endl << Polar(10,4) << endl;
cout << (c1 += c2) << endl;
cout << (c1 == c2) << endl << (c1 != c2) << endl << +c2 << endl; -c2 << endl;
cout << (c2 - 2) << endl << (5 + c2) << endl;
вернуть 0;
Как показано на рисунке выше, самоприсваивание необходимо проверять при перегрузке оператора присваивания «=" по следующим причинам:
Если нет обнаружения самоприсвоения, то m_data собственного объекта будет освобождена, а содержимое, на которое указывает m_data, не будет существовать, поэтому возникнут проблемы с копией.
Вот два класса для объяснения:
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;
};
После создания этих двух объектов компилятор (VC) выделяет память для этих двух объектов следующим образом:
Два слева — это выделение памяти компилятором для комплекса классов в режиме отладки и режиме выпуска. В режиме отладки компилятор вставляет голову и хвост (красная часть), часть информации о размере 4*8 + 4 (серая часть) в память сложного объекта. Зеленая часть — это пространство, фактически занимаемое сложным объектом. в разделе всего 52 слова, но VC выровнен по 16 байтам, поэтому ближайшие 16, кратные 52, равны 64, а 12-байтовый пробел должен быть заполнен (голубая часть клавиатуры). Для сложного объекта в релизной части добавляются только информационный заголовок и заголовочная часть. Анализ класса строк в основном такой же.
Далее давайте посмотрим, как компилятор VC выделяет объекты массива:
Аналогичным образом компилятор добавляет к объекту некоторую избыточную информацию. Для объектов сложного класса, поскольку в массиве три объекта, имеется 8 двойных значений. Затем компилятор вставляет цифру 3 перед тремя сложными объектами, чтобы отметить номер отдельного объекта. . Метод анализа класса String также аналогичен.
На рисунке ниже показано, почему для удаления объекта массива требуется метод delete[]:
Строка.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;
}
В C++ однопараметрический конструктор (или многопараметрический конструктор со значениями по умолчанию для всех параметров, кроме первого параметра) играет две роли. 1 — конструктор, 2 — оператор неявного преобразования типов по умолчанию.
Поэтому иногда, когда мы пишем такой код, как AAA = XXX, а тип XXX оказывается типом параметра однопараметрического конструктора AAA, компилятор автоматически вызывает этот конструктор для создания объекта AAA.
Это выглядит круто и очень удобно. Но в некоторых случаях (см. авторитетный пример ниже) это противоречит нашим (программистам) первоначальным намерениям. На данный момент вам необходимо добавить явную модификацию перед конструктором, чтобы указать, что этот конструктор можно вызывать/использовать только явно и нельзя использовать неявно в качестве оператора преобразования типа.
Явный конструктор используется для предотвращения неявных преобразований. Пожалуйста, посмотрите на код ниже:
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;
}
Конструктор Test1 принимает параметр int, а строка 23 кода будет неявно преобразована в вызов конструктора Test1. Конструктор Test2 объявлен как явный, что означает, что конструктор не может быть вызван посредством неявного преобразования, поэтому в строке 24 кода возникнет ошибка компиляции.
Обычные конструкторы можно вызывать неявно. Явный конструктор можно вызвать только явно.
Когда какую-либо дробь необходимо преобразовать в тип double, для преобразования автоматически вызывается функция double(). Как показано на рисунке выше, компилятор определяет, что 4 является целым числом во время анализа double d = 4 + f, а затем продолжает определять f. Замечено, что f предоставляет функцию double(), а затем выполняет функцию double. () над f и вычисляет 0,6, затем прибавляет его к 4 и, наконец, получает двойной тип 4,6.
На рисунке выше определен класс, который называется Fraction. Оператор «+» перегружен в классе. Во время операции f+4 «4» неявно преобразуется (конструктором) компилятором в объект Fraction, а затем передается. через Fraction В операции участвует перегруженный оператор «+».
Как показано на рисунке выше, к Fraction добавляется функция double(), которая разделяет две переменные-члены Fraction, а затем принудительно преобразует их в тип double и возвращает результат. Во время процесса перегрузки f+4 компилятор будет. сообщить об ошибке Вы можете сделать следующий анализ:
1. Сначала 4 неявно преобразуется (конструктор) в объект Fraction, а затем перегруженный оператор «+» обрабатывается с помощью «f» для возврата объекта Fraction;
2. Сначала 4 неявно преобразуется (конструктор) в объект Fraction, а затем над парой выполняется двойная операция через перегруженный оператор «+» и операцию «f», и, наконец, возвращается объект Fraction;
3. . .
Таким образом, у компилятора есть как минимум два пути, что создает двусмысленность и сообщает об ошибке. Как показано на рисунке выше, если явное ключевое слово добавляется перед конструктором Francion, неявное преобразование будет отменено. Поэтому во время выполнения d2 = f + 4 f вызовет функцию double для преобразования в 0,6, а затем. добавьте его к 4, чтобы получить 4,6. Поскольку конструктор отменяет неявное преобразование формулы, 4,6 невозможно преобразовать в дробь, поэтому будет сообщено об ошибке.
На следующем рисунке показано применение функций перегрузки операторов и преобразования в C++ stl:
Следующий рисунок хорошо иллюстрирует внутреннюю структуру и использование интеллектуальных указателей: В синтаксисе интеллектуальных указателей есть три ключевых момента. Первый — это сохраненный внешний указатель, который соответствует T* px на рисунке выше. связанный с входящим указателем, будет выполнен вместо входящего указателя; второй — перегрузить оператор «*», разыменовать и вернуть объект, на который указывает указатель; третий — перегрузить оператор «->», чтобы return Указатель, соответствующий изображению выше, — это px.
Итераторы также являются разновидностью интеллектуальных указателей. Существуют также три упомянутых выше элемента интеллектуальных указателей, которые соответствуют частям, отмеченным красным шрифтом и желтым цветом на рисунке ниже:
Символы перегрузки итератора «*» и «->» будут тщательно проанализированы ниже:
Создайте объект итератора списка, list::iterator ite; список здесь используется для сохранения объекта Foo, который является классом в определении шаблона списка. T, оператор*() возвращает объект (*node).data, узел имеет тип __link_type, но __link_type имеет тип __list_node<T>*. T здесь Foo, поэтому узел имеет тип __list_node<Foo >*, поэтому ( *node).data получает объект типа Foo, а &(operator*()) наконец получает адрес объекта Foo, то есть возвращает Foo* Указатель на тип.
Как видно из рисунка выше, каждый функтор представляет собой класс, который перегружает оператор «()», а затем становится «функтором». На самом деле это класс, но он имеет свойства функции. На самом деле за каждым функтором стоит странный класс, как показано на рисунке ниже. Этот класс не обязательно должен быть явно объявлен программистом вручную. Функтор в стандартной библиотеке также наследует странный класс: содержимое этого класса показано на рисунке ниже. Он просто объявляет некоторые вещи, и в нем нет реальных переменных или функций. Конкретное содержимое будет обсуждаться в STL.
В отличие от шаблонов классов, шаблонам функций не требуется явно объявлять тип входящих параметров при их использовании, компилятор автоматически определяет тип.
Шаблоны элементов часто используются в универсальном программировании. Чтобы обеспечить лучшую масштабируемость, в качестве примера возьмите приведенный выше рисунок. T1 часто является базовым классом U1, а T2 — базовым классом U2. Вы можете увидеть следующий пример: Таким образом, пока родительскими или предковыми классами U1 и U2 являются T1 и T2, с помощью этого метода можно умело использовать наследование и полиморфизм, но не наоборот. Этот метод часто используется в STL:
Как следует из названия, парциализация шаблона относится к указанию конкретных типов данных в шаблоне, что отличается от обобщения: конечно, парциализация шаблона также имеет степени, и могут быть указаны частичные типы, что называется частичной специализацией:
В курсе C++11 будет объяснено слишком много контента, поэтому я пока представлю его здесь.
В курсе C++11 будет объяснено слишком много контента, а пока я представлю его здесь.
Ссылку можно рассматривать как псевдоним переменной, на которую ссылаются.
Как показано на рисунке выше, определены три класса: A, B и C. B наследуется от A, а C наследуется от B. В A есть две виртуальные функции, одна в B и одна в C. Компилятор выделяет объект a из A в памяти, как показано на рисунке выше. В то же время, поскольку класс A имеет виртуальные функции, компилятор выделит место для объекта a. чтобы сохранить таблицу виртуальных функций, в этой таблице хранится адрес виртуальной функции (динамическая функция) этого класса. Привязка с сохранением состояния), поскольку класс A имеет две виртуальные функции, в таблице виртуальных функций есть два пробела (желтый и синий), указывающие на A::vfunc1() и A::vfunc2() соответственно, b It; является объектом класса B, поскольку класс B переписывает функцию vfunc1() класса A. , поэтому таблица виртуальных функций B (голубая часть) будет указывать на B::vfunc1(), а B наследует vfunc2() класса A, поэтому таблица виртуальных функций B (синяя часть) будет указывать на A родительского класса A: :vfunc2 () аналогично, c является объектом класса C, представленным как; Класс C переписывает функцию vfunc1() родительского класса, поэтому таблица виртуальных функций C (желтая часть) будет указывать на C::vfunc1(). В то же время C наследует vfunc2() суперкласса A, поэтому виртуальная функция B. Таблица (синяя часть) будет указывать на функцию A::vfunc2(). В то же время на приведенном выше рисунке также используется код языка C, чтобы проиллюстрировать, как нижний уровень компилятора вызывает эти функции. В этом суть объектно-ориентированного полиморфизма наследования.
Указатель this на самом деле можно рассматривать как указатель, указывающий на адрес памяти текущего объекта. Как показано на рисунке выше, поскольку в базовом классе и подклассе есть виртуальные функции, this->Serialize() будет динамически привязан. что эквивалентно (*(this- >vptr)[n])(this). Это можно понять, объединив виртуальный указатель и таблицу виртуальных функций из предыдущего раздела. Что касается того, почему в конце концов писать так правильно, это объяснит следующее резюме.