Я молодой аспирант V класса из Бхарата. Мой папа, который также является моим гуру, познакомил меня с компьютерным программированием, когда мне было около шести лет. Начиная с Scratch, затем с Java и затем с Python — я уже рассмотрел многие аспекты этих важных компьютерных языков.

Теперь, когда я планирую углубиться в исходный код Blender, чтобы подняться от простого программиста до настоящего компьютерного ученого, я изучаю C++ у моего Гуру, моего отца.

Что касается меня, очень важно действительно понимать общую картину, я регулярно практикую ведение дневника своего обучения. Сегодня я хочу поделиться своим опытом с миром.

Мы — будущее…

Мое послание всем моим друзьям во вселенной… сделайте прыжок веры и никогда не сдавайтесь…

Это непрерывный процесс. Поэтому я буду регулярно обновлять это пространство. Пока здесь столько всего. Следите за обновлениями….

Канонический класс

Канонический класс — это определяемый пользователем тип (UDT).

Другие типы являются встроенными типами, такими как int, float, long и т. д.

Канонический класс — это раскрытая версия пользовательского класса. Есть четыре важных точных компонента, необходимых для создания канонического класса в C++.

Ингредиенты:

  • Конструктор по умолчанию — это конструктор без каких-либо параметров.
  • Конструктор копирования — с помощью этого конструктора вы можете скопировать характеристику исходного объекта в клон и сохранить постоянный статус исходного объекта.
  • Оператор присваивания — это как x=y — значение y перезапишет x. поэтому объект LHS будет перезаписан объектом RHS
  • Виртуальный деструктор — для очистки ресурсов.

Основная идея состоит в том, чтобы заставить UDT вести себя как встроенный тип. Позвольте мне объяснить вам. Предположим, я создал класс UDT с именем Integer, который представляет фактический целочисленный тип данных (так же, как классы Java Boxing). Итак, позвольте мне создать объект x класса. x = 5. Таким образом, я создал объект y. Теперь, если я напишу что-то вроде x = y, y перезапишет x. Как это происходит? Потому что я создал класс UDT Integer как канонический класс — точнее из-за оператора присваивания это возможно. Итак, наша главная цель здесь — сделать его как можно ближе к реальному встроенному типу.

Указатель

Основы указателя — я пропустил большинство из них, потому что начал с java и python.

Язык со строгой типизацией

Тип данных очень важен в C++. В C++ каждый встроенный тип имеет определенный размер. Таким образом, char имеет 1 байт, int имеет 4 байта, а long и double имеют 8. Поэтому, когда я увеличиваю указатель (ptr++), тип встроенного типа, на который указывает указатель, имеет значение, потому что, если это char (char*ptr), так как символ имеет 1 байт, то указатель увеличится на единицу, а если это целое число (int* ptr), то увеличится на четыре байта, а если он длинный или двойной, то увеличится на 8 байт. Итак, вы видите, что тип очень важен в C++ для правильного функционирования. И та же логика применима к UDT.

Исходный код канонического класса «MyString1»

/*
* MyString1.h
*
*  Created on: 19-Jan-2022
*      Author: ridit
*/
#ifndef MYSTRING1_H_
#define MYSTRING1_H_

#include <cstddef>
#include <string.h>
#include <boost/uuid/uuid.hpp>            // uuid class
#include <boost/uuid/uuid_generators.hpp> // generators
#include <boost/uuid/uuid_io.hpp>         // streaming operators etc.
#include "StrUtility.h"
class MyString1 {
private:
char* data;
unsigned int length;
boost::uuids::uuid tag;
public:
MyString1():tag( boost::uuids::nil_uuid()){
data = NULL;
length = 0;
}
MyString1(const char* str): tag( boost::uuids::random_generator()()){
int l = StrUtility::strlen(str) ;
data = new char[l+1];
StrUtility::memcpy(data, str, l+1);
length=StrUtility::strlen(data);
}
virtual ~MyString1();//destructor
MyString1(const MyString1 &other);//copy constructor
MyString1& operator=(const MyString1 &other);//assignment operator
inline char* c_str()
{
return data;
}
inline const char* c_str() const
{
return data;
}
inline boost::uuids::uuid getTag()
{
return tag;
}
MyString1& operator+=(const MyString1& other);
MyString1& operator+=(const char* inStr);
bool operator==(const MyString1& other);
bool operator==(char* ptr);
};
#endif /* MYSTRING1_H_ */
/*
* MyString1.cpp
*
*  Created on: 19-Jan-2022
*      Author: ridit
*/
#include "MyString1.h"
MyString1::MyString1(const MyString1 &other) {
int l = other.length;
data = new char[l + 1];
StrUtility::memcpy(data,other.data,l+1);
length=StrUtility::strlen(data);
tag =  boost::uuids::random_generator()();
}
MyString1& MyString1::operator=(const MyString1 &other) {
// TODO Auto-generated method stub
if(this == &other)
return *this;
delete[] data;
int l = other.length;
data = new char[l+1];
StrUtility::memcpy(data, other.c_str(), l+1);
length=StrUtility::strlen(data);
tag =  boost::uuids::random_generator()();
return *this;
}
MyString1& MyString1::operator+=(const MyString1& other){
int l = length + other.length;
char* temp = new char[l+1];
StrUtility::memcpy(temp, data, length+1);
delete[] data;
data = NULL;
StrUtility::strcat(temp, other.data);
data = temp;
length = StrUtility::strlen(data);
return *this;
}
MyString1& MyString1::operator+=(const char* inStr) {
int l = length + StrUtility::strlen(inStr);
char* temp = new char[l+1];
StrUtility::memcpy(temp, data, length+1);
delete[] data;
data = NULL;
StrUtility::strcat(temp,inStr);
data = temp;
length = StrUtility::strlen(data);
return *this;
}
bool MyString1::operator ==(const MyString1 &other) {
if(StrUtility::isEqual(data, other.data)){
return true;
}
return false;
}
bool MyString1::operator == (char *ptr) {
if(StrUtility::isEqual(data, ptr)){
return true;
}
return false;
}
MyString1::~MyString1() {
if(data != NULL){
cout<<"deleting the resource containing the text " << data << " of the object " <<tag<<endl;
delete[] data;
data = NULL;
}
}
/*
* StrUtility.h
*
*  Created on: Jan 17, 2022
*    
*/
#ifndef STRUTILITY_H_
#define STRUTILITY_H_
#include <cstddef>
#include <iostream>
using namespace std;
class StrUtility {
public:
static void *memcpy(void *dst, const void *src, size_t len);
static size_t strlen(const char *str);
static char* strcat(char *dest, const char *src);
static bool isEqual(char* str1,char* str2);
StrUtility();
virtual ~StrUtility();
};
#endif /* STRUTILITY_H_ */
#include "StrUtility.h"
StrUtility::StrUtility() {
// TODO Auto-generated constructor stub
}
StrUtility::~StrUtility() {
// TODO Auto-generated destructor stub
}
void* StrUtility::memcpy(void *dst, const void *src, size_t len)
{
size_t i;
/*
* memcpy does not support overlapping buffers, so always do it
* forwards. (Don't change this without adjusting memmove.)
*
* For speedy copying, optimize the common case where both pointers
* and the length are word-aligned, and copy word-at-a-time instead
* of byte-at-a-time. Otherwise, copy by bytes.
*
* The alignment logic below should be portable. We rely on
* the compiler to be reasonably intelligent about optimizing
* the divides and modulos out. Fortunately, it is.
*/
if ((uintptr_t)dst % sizeof(long) == 0 &&
(uintptr_t)src % sizeof(long) == 0 &&
len % sizeof(long) == 0) {
long *d = dst;
const long *s = src;
for (i=0; i<len/sizeof(long); i++) {
d[i] = s[i];
}
}
else {
char *d = dst;
const char *s = src;
for (i=0; i<len; i++) {
d[i] = s[i];
}
}
return dst;
}
size_t StrUtility::strlen(const char *str) {
const char *s;
for (s = str; *s; ++s)
;
return (s - str);
}
char* StrUtility::strcat(char *dest, const char *src){
char *rdest = dest;
while (*dest)
dest++;
while (*dest++ = *src++)
;
return rdest;
}
bool StrUtility::isEqual(char *str1, char *str2) {
if(strlen(str1) != strlen(str2)){
return false;
}
int length = strlen(str1);
while(*str1 != '\0'){
if(*str1 != *str2){
return false;
}
str1++;
str2++;
}
return true;
}

Глубокое копирование против. Неглубокое копирование

Глубокое копирование

В случае глубокого копирования мы создаем новый буфер для хранения data* ptr. так что str1 будет указывать на свой собственный буфер, а str2 будет указывать на свой собственный. Когда область видимости объекта выйдет наружу, деструктор уничтожит собственный буфер. таким образом, он избежит проблемы двойного удаления. Таким образом, это означает, что все переменные-члены указателя должны иметь свое собственное пространство в копии, в отличие от мелкой копии, которая является мелкой, глубокая копия имеет глубину.

Неглубокое копирование

В неглубокой копии мы не создаем никаких новых буферов. поэтому могут возникнуть некоторые проблемы. Позвольте мне объяснить вам. Предположим, что есть два объекта str1 и str2. Итак, внутри str1 есть data* ptr и l (длина). Даже внутри str2 будут обе эти переменные-члены. Предположим, мы создаем объект str2 с данными ridit. поэтому data* ptr будет указывать на начало этого массива. Если мы создадим строку str1 после поверхностного копирования строки str2, то data* ptr строки str1 будет указывать на начало одного и того же массива, что означает, что два указателя указывают на один и тот же массив.

когда они придут к деструктору, они будут удалены один за другим. При первом удалении указатель станет нулевым. Таким образом, другой указатель другого объекта теперь будет указывать на ноль. когда вызывается деструктор этого объекта, он снова попытается удалить указатель, который уже равен нулю. это известно как проблема двойного удаления. Это исключительная ситуация.

Встроенная функция

inline char* c_str()
 {
 return data;
 }

Метод – это преданный работник. Когда мы вызываем метод, сначала создается стек для метода.

В современной компьютерной архитектуре существует нечто, известное как счетчик программr, который помогает собирать инструкции и затем помещать их в АЛУ или операционную систему. поэтому, когда вызывается метод, программный счетчик переходит к начальному адресу метода, и метод запускается.

После завершения метода происходит раскручивание стека этого метода. Следовательно, для вызова метода требуется много ресурсов.

Теперь давайте подумаем, почему встроенная функция появилась на первом месте. Если проекту приходится много раз вызывать небольшой метод, ему придется повторять процесс много раз. Это замедлится из-за накладных расходов, которые нам нужны для вызова метода. Для этого существует набор крошечных полезных методов, известных как встроенные методы, при вызове которых их код будет просто вставлен в место вызова вместо перехода. Итак, в чем особенности таких методов:

  1. Он должен быть простым и маленьким
  2. Предшествует ключевому слову inline.
  3. Это всегда написано в заголовочном файле на C++
  4. Не требует много ресурсов
  5. Когда дело доходит до счетчика программ, он вставляет код

Счетчик команд

Так что же такое программный счетчик? Он содержит адрес следующей инструкции для обработки процессором.

Увеличить UUID

Поэтому мне было любопытно посмотреть, есть ли способ получить идентификацию объекта, как в Java есть хэш-код, связанный с каждым объектом. Итак, мой лучший папа или гуру и я обсудили, а затем использовали UUID библиотеки повышения для создания идентификатора объекта. Теперь он работает отлично.

Затем мы изменим переменную-член необработанного указателя на уникальный указательr. Уникальный указатель — это указатель, который автоматически уничтожается всякий раз, когда выходит за пределы области видимости.

Теперь я хочу рассказать вам историю, которую мне рассказал мой отец, о том, как библиотека boost стала частью стандартной библиотеки C++. Вы знаете, что современные телефоны Nokia способны обрабатывать огромные объемы данных. Итак, в те дни, когда памяти было мало, команда Nokia/Symbian была вынуждена выполнять особый процесс построения объектов, который назывался Двухэтапное построение, потому что инженерам C++ было очень тяжело работать с памятью. исключения в конструкторе. Постепенно я буду подробно объяснять все эти вещи.

Копировать конструктор

MyString1::MyString1(const MyString1& str) {
int l = StrUtility::strlen(str.data);
 data = new char[l + 1];
 StrUtility::memcpy(data,str.data,l+1);
 length=StrUtility::strlen(str.data);
 tag = boost::uuids::random_generator()();
}

Итак, в прошлый раз я говорил вам, что с помощью этого конструктора вы можете скопировать характеристики исходного объекта в клон и сохранить исходный статус объекта нетронутым. Теперь позвольте мне сказать вам, что когда мы вызываем метод, создается стек для вызова этого метода. В этом стеке создаются копии параметров метода. Оригиналы хранятся в сохранности. Таким образом, всякий раз, когда мы создаем копию, именно конструктор копии вызывается для создания копии любого определяемого пользователем типа. Теперь, если мы видим код, перед параметром стоит амперсанд (&). Это называется передачей по ссылке.

На самом деле существует три способа передачи параметров в функцию:

  1. Передать по значению
  2. Пройти по указателю
  3. Пройти по ссылке

В первом случае в стеке создаются копии параметров. Во втором случае в стеке сохраняется указатель на исходный объект. в третьем случае в стеке сохраняется ссылка на исходный объект.

Итак, что произойдет, если в конструкторе копирования вместо передачи по ссылке мы изменим его на передачу по значению? Что ж, система хочет создать копию исходного объекта, а затем вызовет для этого конструктор копирования. Конструктор копирования снова попытается создать копию и снова вызовет конструктор копирования. И это будет продолжаться. И мы получим переполнение стека. Система пойдет на бросок. Вот почему мы используем передачу по ссылке в случае конструктора копирования.

Перегрузка оператора

MyString1& MyString1::operator+=(const MyString1& other){
int l = length + other.length;
char* temp = new char[l+1];
StrUtility::memcpy(temp, data, length+1);
delete[] data;
data = NULL;
StrUtility::strcat(temp, other.data);
data = temp;
length = StrUtility::strlen(data);
return *this;
}

Мы придаем новое значение конкретному оператору для наших собственных целей.

Мы знаем, что x +=y => x = x + y для двух чисел.

Точно так же здесь, если мы выполним str1 += str2, это соединит str2 в конце str1.

В отличие от класса Java String, мы не сделали наш класс String неизменяемым. Итак, здесь мы можем изменить буфер, который фактически содержит данные. Поэтому внутри метода перегрузки оператора мы создали новый буфер. Мы создали копию буфера и удалили оригинал, используя delete[]data. После удаления мы обнулили его, что является хорошей практикой программирования на C++. Вы можете видеть, что мы не меняем тег или идентификатор объекта левого объекта, потому что наш класс String не является неизменным и, следовательно, после операции мы сохраняем тот же старый объект (в отличие от Java, который создает совершенно новый объект String). ).

MyString1& MyString1::operator=(const char * inStr) {
int l = StrUtility::strlen(inStr);
data = new char[l+1];
StrUtility::memcpy(data, inStr, l);
length = l;
tag =  boost::uuids::random_generator()();
return *this;
}

Вам должно быть интересно, почему мы возвращаем *this.

Подумайте о следующем фрагменте кода:

MyString1 title2;
MyString1 title1 = title2 = " Mukhopadhyay";

Так как присваивание работает справа налево, первый title2 присваивается необработанным указателем с использованием вышеперегруженной версии оператора присваивания.

Если вы слышали словоАхм, оно означает меня. Это санскритский термин для меня. Так почему же мы возвращаем *this. Предположим, title2 говорит, что здесь я родился. Если вы хотите работать со мной, то берите меня, а не какого-то моего клона. Прими меня таким как есть. Ахм. Говорит возьми меня. Поэтому, если мы не вернем *this, ahm не будет возвращено. ахм очень важен.