1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
//Листинг #0 Пример-основа к статье Оборачивание Си-строки классом clang #include <iostream> #include <cstring> //strcpy, strlen using std::cout; using std::ostream; class MyString{ unsigned len; //длина строки char* str; //указатеьная переменная в качестве строки public: static unsigned str_count; //необязательный для класса, но полезный для описания элемент: количество объектов класса MyString(); //прототип конструктора по умолчанию MyString(const char* S); //прототип конвертирующего конструктора (конструктора с одним параметром) ~MyString(); //прототип деструктора friend ostream& operator << (ostream& os, const MyString& S); //перегрузка << для (cout << S) }; unsigned MyString::str_count = 0; //инициализировали счётчик объектов нулём /* реализация конструктора по умолчанию */ MyString::MyString(){ len = 4; str = new char[len]; strcpy(str, "C++"); //по умолчанию создаём строку "С++" str_count++; //наращиваем счётчик объектов } /* реализация конструктора с параметром */ MyString::MyString(const char* S){ len = strlen(S); str = new char[len + 1]; //+1 нужен для нуль-символа, признака конца строки strcpy(str, S); //создаём строку на основании значения параметра str[len] = '\0'; //на всякий случай, если строка будет слишком большой, насильно перепишем последний символ, если вдруг конца в строке не окажется str_count++; //наращиваем счётчик объектов } MyString:: ~MyString(){ delete []str; str_count--; //с каждым разрушенным объектом счётчик объектов уменьшается } ostream& operator << (ostream& os, const MyString& S){ return os << S.str; } int main(){ MyString S1 = "Hello"; MyString S2; cout << "S1 == " << S1 << '\n'; cout << "S2 == " << S2 << '\n'; cout << "count strings == " << MyString::str_count << '\n'; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
//Листинг #1 Написание своего класса string clang #include <iostream> #include <cstring> //strcpy, strlen using std::cout; using std::ostream; class MyString{ unsigned len; //длина строки char* str; //указатеьная переменная в качестве строки public: static unsigned str_count; //необязательный для класса, но полезный для описания элемент: количество объектов класса MyString(); //прототип конструктора по умолчанию MyString(const char* S); //прототип конвертирующего конструктора (конструктора с одним параметром) ~MyString(); //прототип деструктора friend ostream& operator << (ostream& os, const MyString& S); //перегрузка << для (cout << S) }; unsigned MyString::str_count = 0; //инициализировали счётчик объектов нулём /* реализация конструктора по умолчанию */ MyString::MyString(){ len = 4; str = new char[len]; strcpy(str, "C++"); //по умолчанию создаём строку "С++" str_count++; //наращиваем счётчик объектов cout << str_count << " == " << str << " default object created\n"; } /* реализация конструктора с параметром */ MyString::MyString(const char* S){ len = strlen(S); str = new char[len + 1]; //+1 нужен для нуль-символа, признака конца строки strcpy(str, S); //создаём строку на основании значения параметра str[len] = '\0'; //на всякий случай, если строка будет слишком большой, насильно перепишем последний символ, если вдруг конца в строке не окажется str_count++; //наращиваем счётчик объектов cout << str_count << " == " << str << " object created\n"; } MyString:: ~MyString(){ cout << str << " object deleted\n"; delete []str; str_count--; //с каждым разрушенным объектом счётчик объектов уменьшается cout << str_count << " left\n"; //осталось объектов столько-то } ostream& operator << (ostream& os, const MyString& S){ return os << S.str; } int main(){ { MyString S1 = "Hello"; MyString S2; cout << "S1 == " << S1 << '\n'; cout << "S2 == " << S2 << '\n'; cout << "count strings == " << MyString::str_count << '\n'; } } |
1 2 3 4 5 6 7 8 9 10 |
//Листинг #a1 Двойное delete Неверная попытка присваивания int main() { char *ch1 = new char[255]{ "" }; char *ch2 = new char[255]{ " string " }; ch1 = ch2; delete[]ch1, delete []ch2; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 |
//Листинг #2 Написание класса строка Листинг с дефектами //добавим две функции, в которых объект класса будет выступать в роли параметра. В одном случае как параметр ссыочного характер, в другом как параметр по значению (функции callme1, callme2) #include <iostream> #include <cstring> //strcpy, strlen using std::cout; using std::ostream; using std::endl; class MyString{ unsigned len; //длина строки char* str; //указатеьная переменная в качестве строки public: static unsigned str_count; //необязательный для класса, но полезный для описания элемент: количество объектов класса MyString(); //прототип конструктора по умолчанию MyString(const char* S); //прототип конвертирующего конструктора (конструктора с одним параметром) ~MyString(); //прототип деструктора friend ostream& operator << (ostream& os, const MyString& S); //перегрузка << для (cout << S) }; unsigned MyString::str_count = 0; //инициализировали счётчик объектов нулём /* реализация конструктора по умолчанию */ MyString::MyString(){ len = 4; str = new char[len]; strcpy(str, "C++"); //по умолчанию создаём строку "С++" str_count++; //наращиваем счётчик объектов cout << str_count << " == " << str << " default object created\n"; } /* реализация конструктора с параметром */ MyString::MyString(const char* S){ len = strlen(S); str = new char[len + 1]; //+1 нужен для нуль-символа, призкака конца строки strcpy(str, S); //создаём строку на основании значения параметра str[len] = '\0'; //на всякий случай, если строка будет слишком большой, насильно перепишем последний символ, если вдруг конца в строке не окажется str_count++; //наращиваем счётчик объектов cout << str_count << " == " << str << " object created\n"; } MyString:: ~MyString(){ cout << str << " object deleted\n"; delete []str; str_count--; //с каждым разрушенным объектом счётчик объектов уменьшается cout << str_count << " left\n"; //осталось объектов столько-то } ostream& operator << (ostream& os, const MyString& S){ return os << S.str; } void callme1(MyString& S){ //параметр как ссылка cout << "reference parametr:\n" << S << '\n'; //информационное сообщение, что параметр по ссылке } void callme2(MyString S){ //параметр как ссылка cout << "value parametr:\n" << S << '\n'; //инфосообщение, что параметр по значению } int main(){ { cout << "Block start\n"; MyString S1("S1, ctor with param"); //создание объектов с помощью конструктора с параметром MyString S2("S2, ctor with param"); MyString S3("S3, ctor with param"); cout << "S1: " << S1 << endl; //вывод созданных строк на экран cout << "S2: " << S2 << endl; cout << "S3: " << S3 << endl; callme1(S1); //вызов функции с параметром-ссылкой cout << "S1: " << S1 << endl; callme2(S2); //вызов функции с параметром-значением cout << "S2: " << S2 << endl; cout << "Initialize one object to another:\n"; MyString S4 = S3; //используем конвертирующий конструктор для инициализации объекта объектом cout << "S4: " << S4 << endl; cout << "Assign one object to another:\n"; MyString S5; S5 = S1; //присваивание объекта в объект cout << "S5: " << S5 << endl; cout << "Exiting the block.\n"; } cout << "End of main()\n"; } |
1 2 3 4 5 6 7 8 |
//Листинг #a2 Конструктор по умолчанию всегда создаетя: если не создавать явно, создастся неявно class MyClass {}; //в классе не объявлено явно ни одного конструктора int main() { MyClass x; //x выглядит как обычная автоматическая переменная //состояние x неизвестно } |
1 2 3 4 5 6 7 8 9 10 11 |
//Листинг #a3 Конструктор по умолчанию не генерируется компилятором, если в классе хоть один конструктор class MyClass { public: MyClass(int x){} //один конструктор написан }; int main() { MyClass x; //ошибка компиляции, нет конструктора по умолчанию } |
1 2 3 4 5 6 7 8 9 10 11 12 13 |
//Листинг #a4 class MyClass { int x; public: MyClass() { x = 100; //инициализировали x значением 100 } }; int main() { MyClass x; //состояние x инициализировано неявно, явно описанным конструктором по умолчанию } |
1 2 3 4 5 6 7 8 9 10 11 12 13 |
//Листинг #a5 class MyClass { int x; public: MyClass(int value = 100) { //параметр конструктора имеет значение по умолчанию x = value; } }; int main() { MyClass x; //состояние x инициализируется неявно, с помощью явно описанного конструктора по умолчанию } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
//Листинг #a6 class MyClass { int x; public: MyClass(int value = 100) {} //конструктор с параметром может быть конструктором по умолчанию MyClass(){} //явный конструктор по умолчанию }; int main() { MyClass x; //состояние x инициализируется неявно, с помощью явно описанного конструктора по умолчанию //неоднозначность: два конструктора по умолчанию, ошибка компиляции } |
1 |
String(const String &); //прототип конструктора копирования для класса String |
О конструкторе копирования нужно знать два момента: когда он используется и что он делает.
1 2 3 4 5 |
//Листинг #a7 String ditto(motto); // вызывает String(const String &) String metoo = motto; // вызывает String(const String &) String also = String(motto); // вызывает String(const String &) String * pString = new String(motto); // вызывает StringBad(const StringBad &) |
1 |
callme2(S2); |
В программе конструктор копирования применяется для инициализации S — формального параметра типа MyString для функции callme2 () (смотрите функцию callme2() и её параметры). Тот факт, что при передаче объекта по значению вызывается конструктор копирования, является хорошей причиной для передачи по ссылке. Это позволит сэкономить время вызова конструктора и память для хранения нового объекта.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
//Листинг #a8 #include <iostream> using std::cout; using std::cin; using std::ostream; class MyClass { char *x; public: MyClass() :x(new char[50]{ "string created by ctor default" }) {} //конструктором по умолчанию выделяем память friend ostream& operator << (ostream& os, const MyClass& object) { return os << object.x; } //для cout << MyClass(const MyClass&) { //копирующий конструктор, навскидку в том же виде, в каком его генерирует компилятор } }; int main() { MyClass S1; cout << "S1 == " << S1 << '\n'; cout << "worked copy ctor:\n"; //смотрим н работу копирующего конструктора MyClass S2(S1); //использование копирующего конструктора для инициализации S2 состоянием S1 cout << "S2 == " << S2 << '\n'; //а память-то выделена не была, хотя S2.x был перенаправлен на S1.x //S2.x всего-лишь указатель, не связанный с большим блоком памяти cin.get(); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
//Листинг #a9 #include <iostream> using std::cout; using std::cin; using std::ostream; class MyClass { char *x; public: MyClass() :x(new char[50]{ "string created by ctor default" }) {} //конструктором по умолчанию выделяем память friend ostream& operator << (ostream& os, const MyClass& object) { return os << object.x; } //для cout << MyClass(const MyClass& S) { //копирующий конструктор, в том же виде, в каком его генерирует компилятор } ~MyClass(){ delete[]x; } }; MyClass foo(MyClass object) { //функция принимает объекты по значению return MyClass(object); //используем конструктор копирования для инициализации возвращаемого значения } int main() { MyClass S1, S2; S2 = foo(S1); //Двойное delete [] } |
1 2 3 4 5 6 7 8 9 10 11 12 |
//Листинг #a10 class MyClass { }; int main() { MyClass T1, T2; T1 = T2; //объекты легко присваиваются T2 = T1; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
//Листинг #a11 class MyClass { int x; char* ch; public: const MyClass & MyClass::operator=(const MyClass &object) { /*копируются элементы*/ x = object.x; ch = object.ch; /*возвращается ссылка на самого себя, но по состоянию уже обновлённого себя*/ return *this; } }; int main() { MyClass T1, T2; T1 = T2; //объекты легко присваиваются T2 = T1; } |
1 2 |
MyString S1,S2; //объявления, либо создание объектов S1 = S2; //присваивание |
1 2 |
MyString S1 = S2; // используется конструктор копирования, //но возможно и присваивание |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 |
//Листинг #3 Леченеие проблем путём применения правила трёх Написание своего класса, представляющего из себя строку clang #include <iostream> #include <cstring> //strcpy, strlen using std::cout; using std::ostream; using std::endl; class MyString{ unsigned len; //длина строки char* str; //указатеьная переменная в качестве строки public: static unsigned str_count; //необязательный для класса, но полезный для описания элемент: количество объектов класса MyString(); //прототип конструктора по умолчанию MyString(const char* S); //прототип конвертирующего конструктора (конструктора с одним параметром) friend ostream& operator << (ostream& os, const MyString& S); //перегрузка << для (cout << S) /*ЯВНО ОПИСЫВАЕМ ВСЕ ТРИ НЕОБХОДИМЫЕ ФУНКЦИИ, КОТОРЫЕ ДО ЭТОГО ГЕНЕРИРОВАЛИСЬ КОМПИЛЯТОРОМ*/ MyString(const MyString&); //прототип конструктора копии const MyString& operator= (const MyString&); //прототип перегрузки операции присваивания ~MyString(); //прототип деструктора }; unsigned MyString::str_count = 0; //инициализировали счётчик объектов нулём /* реализация конструктора по умолчанию */ MyString::MyString(){ len = 4; str = new char[len]; strcpy(str, "C++"); //по умолчанию создаём строку "С++" str_count++; //наращиваем счётчик объектов cout << str_count << " == " << str << " default object created\n"; } /* реализация конструктора с параметром */ MyString::MyString(const char* S){ len = strlen(S); str = new char[len + 1]; //+1 нужен для нуль-символа, призкака конца строки strcpy(str, S); //создаём строку на основании значения параметра str[len] = '\0'; //на всякий случай, если строка будет слишком большой, насильно перепишем последний символ, если вдруг конца в строке не окажется str_count++; //наращиваем счётчик объектов cout << str_count << " == " << str << " object created\n"; } MyString:: ~MyString(){ cout << str << " object deleted\n"; delete []str; str_count--; //с каждым разрушенным объектом счётчик объектов уменьшается cout << str_count << " left\n"; //осталось объектов столько-то } ostream& operator << (ostream& os, const MyString& S){ return os << S.str; } /* РЕАЛИЗАЦИЯ КОНСТРУКТОРА КОПИРОВАНИЯ */ MyString::MyString(const MyString& object){ len = object.len; //скопировали длину строки из вошедшего в параметр конструктора ззначения str = new char[len + 1]; //в каждом конструкторе нужно выделять памяти, чтобы хватило на строку strcpy(str, object.str); //копируем в выделенную область памяти значения из другой области памяти str_count++; //не забываем, что сейчас мы ведём учёт объектов, в кждом конструкторе надо учитывать } const MyString& MyString::operator=(const MyString& object){ if (this == &object) return *this; //проверка на присваивание самому себе себя //полное копирование состояния копируемого объекта в текущий объект len = object.len; strcpy(str, object.str); //нужно копировать все значимые элементы (статические не имеет смысла, потому что один статический разделяется всеми объектами) return *this; //возвращаем ссылку на обновлённого самого себя } void callme1(MyString& S){ //параметр как ссылка cout << "reference parametr:\n" << S << '\n'; //информационное сообщение, что параметр по ссылке } void callme2(MyString S){ //параметр как ссылка cout << "value parametr:\n" << S << '\n'; //инфосообщение, что параметр по значению } int main(){ { cout << "Block start\n"; MyString S1("S1, ctor with param"); //создание объектов с помощью конструктора с параметром MyString S2("S2, ctor with param"); MyString S3("S3, ctor with param"); cout << "S1: " << S1 << endl; //вывод созданных строк на экран cout << "S2: " << S2 << endl; cout << "S3: " << S3 << endl; callme1(S1); //вызов функции с параметром-ссылкой cout << "S1: " << S1 << endl; callme2(S2); //вызов функции с параметром-значением cout << "S2: " << S2 << endl; cout << "Initialize one object to another:\n"; MyString S4 = S3; //используем конвертирующий конструктор для инициализации объекта объектом cout << "S4: " << S4 << endl; cout << "Assign one object to another:\n"; MyString S5; S5 = S1; //присваивание объекта в объект cout << "S5: " << S5 << endl; cout << "Exiting the block.\n"; } cout << "End of main()\n"; } |
1 |
int *p = 0;; delete p; delete p; //к нулевому указателю безвредно |
1 2 3 4 5 |
//НО! int *p = 0; delete p; p = new int[0]; delete []p; //обязательно следовать если со скобками, то со скобками |
Спасибо.
Очень полезно и доходчиво.