Skip to content

Latest commit

 

History

History
403 lines (358 loc) · 10.3 KB

File metadata and controls

403 lines (358 loc) · 10.3 KB

实现字符串对象

字符串对象的底层可以是char *_pstr;然后实现四大函数-析构,构造,赋值构造,拷贝构造

class String
{
public:
	String(const char *p = nullptr)
	{
		if (p != nullptr)
		{
			_pstr = new char[strlen(p) + 1]; // strlen是有效字符的个数
			strcpy(_pstr, p);
		}
		else
		{
			_pstr = new char[1]; // 为空也开辟一个空间赋值为'\0'
			*_pstr = '\0';
		}
	}
	~String() 
	{
		delete[]_pstr;
		_pstr = nullptr;
	}
	String(const String &str) // 拷贝构造函数
	{
		_pstr = new char[strlen(str._pstr) + 1];
		strcpy(_pstr, str._pstr);
	}
	String& operator=(const String &str) // 赋值构造函数
	{
		if (this == &str) // 防止自赋值
			return *this;

		delete[]_pstr; // 释放原先的内存

		_pstr = new char[strlen(str._pstr) + 1];
		strcpy(_pstr, str._pstr);
		return *this;
	}
private:
	char *_pstr;
};

接下来在public属性中写String的大于小于等于和括号运算符的重载

class String
{
public:
	// 省略
	bool operator>(const String &str)const
	{
		return strcmp(_pstr, str._pstr) > 0;
	}
	bool operator<(const String &str)const
	{
		return strcmp(_pstr, str._pstr) < 0;
	}
		bool operator==(const String &str)const
	{
		return strcmp(_pstr, str._pstr) == 0;
	}
	int length()const { return strlen(_pstr); } // 返回字符串长度
	const char* c_str()const { return _pstr; }  // 转为C语言格式的字符串
	// char ch = str6[6];  str6[6] = '7'
	char& operator[](int index) { return _pstr[index]; } // 重载[]运算符,普通方法可修改
	// char ch = str6[6];  不允许修改!str6[6] = '7'
	const char& operator[](int index)const { return _pstr[index]; } // 常方法不允许修改
// 省略
};

然后在public属性中实现迭代器方法。

首先要了解迭代器的使用:

#include <string>
using namespace std;
int main()
{
	string str1 = "hello world!";
	for(std::string::iterator it = str1.begin(); it != str1.end(); ++it)
	{
		cout << *it << " ";
	}
}

str1.begin()访问到的是数据中的'h'zi字母,str1.end()实际上是最后一位的后继,所以这一位是访问不到的也被作为判断字符串的结束位置。

迭代器可以透明的访问容器内部的元素的值。

下面来实现迭代器,每种容器的迭代器往往有不同的操作,所以迭代器往往内嵌到类中。

class String
{

public:
	// 省略
	// 给String字符串类型提供迭代器的实现
	class iterator
	{
	public:
		iterator(char *p = nullptr) :_p(p) {}
		bool operator!=(const iterator &it) // 实现!=的重载
		{
			return _p != it._p; // 当前迭代器指针 _p 与 迭代器指针it._p进行比较
		}
		void operator++() // 实现++的重载
		{
			++_p;
		}
		char& operator*() { return *_p; } // 迭代器解引用
	private:
		char *_p; // 用指针来访问String底层的每一个char型元素
	};
	// begin返回的是容器底层首元素的迭代器的表示
	iterator begin() { return iterator(_pstr); }
	// end返回的是容器末尾元素后继位置的迭代器的表示
	iterator end() { return iterator(_pstr + length()); }
// 省略
}

完整源文件 点此

迭代器失效

迭代器失效的原因:

在STL里,不能以指针来看待迭代器,指针是与内存绑定的,而迭代器是与容器里的元素绑定的,删除了之后,该迭代器就失效了,在对其重新赋值之前,不能再访问此迭代器。insert也类似,迭代器与元素绑定,当前元素被insert的元素占了自然失效了。

迭代器的失效问题?

  1. 迭代器为什么会失效? a:当容器调用erase方法后,当前位置到容器末尾元素的所有的迭代器全部失效了 b:当容器调用insert方法后,当前位置到容器末尾元素的所有的迭代器全部失效了
     迭代器依然有效    迭代器全部失效
首元素   ->  插入点/删除点  ->  末尾元素
c:insert来说,如果引起容器内存扩容
   原来容器的所有的迭代器就全部失效了
首元素   ->  插入点/删除点  ->  末尾元素
d:不同容器的迭代器是不能进行比较运算的
  1. 迭代器失效了以后,问题该怎么解决? 对插入/删除点的迭代器进行更新操作
#include <ctime>
#include <iostream>
#include <vector>
using namespace std;
int main()
{
    // 1.场景1
    vector<int> vec;
    for(int i = 0; i < 20; ++i)
    {
        vec.push_back(rand() % 100 + 1);
    }

    // 把vec容器中所有的偶数全部删除
    auto it = vec.begin();
    for(; it != vec.end(); ++it)
    {
        if(*it % 2 == 0)
        {
            // 迭代器失效的问题,第一次调用erase以后,迭代器it就失效了
            vec.erase(it); // insert(it, val) erase(it)
            //break; // 用break只删除一个运行正确,去掉break进程出现异常
        }
    }

    // 2. 场景2
    vector<int> vec1;
    for(int i = 0; i < 20; ++i)
    {
        vec1.push_back(rand() % 100 + 1);
    }
    // 给vec容器中所有的偶数前面添加一个小于偶数值1的数字
	auto it1 = vec1.begin();
	for (; it1 != vec1.end(); ++it1)
	{
		if (*it1 % 2 == 0)
		{
			// 这里的迭代器在第一次insert之后,iterator就失效了
			vec1.insert(it1, *it1-1);
			//++it1;
			//break;
		}
	}
	return 0;
}

怎么解决呢?看如下代码

#include <ctime>
#include <iostream>
#include <vector>
using namespace std;
int main()
{
    // 1.场景1
    vector<int> vec;
    for(int i = 0; i < 20; ++i)
    {
        vec.push_back(rand() % 100 + 1);
    }
    for ( int v : vec)
    {
        cout << v << " ";
    }
    cout << endl;

    // 把vec容器中所有的偶数全部删除
    auto it = vec.begin();
    for(; it != vec.end(); ) // 返回了新的iterator,当没有删除操作时才需要++it
    {
        if(*it % 2 == 0)
        {
            it = vec.erase(it); // insert(it, val) erase(it)
            //break; // 用break只删除一个运行正确,去掉break进程出现异常
        }
        else
        {
            ++it;
        }
    }

    for ( int v : vec)
    {
        cout << v << " ";
    }
    cout << endl;

    // 2. 场景2
    vector<int> vec1;
    for(int i = 0; i < 20; ++i)
    {
        vec1.push_back(rand() % 100 + 1);
    }
    // 给vec容器中所有的偶数前面添加一个小于偶数值1的数字
	auto it1 = vec1.begin();
	for (; it1 != vec1.end(); ++it1)
	{
		if (*it1 % 2 == 0)
		{
			it1 = vec1.insert(it1, *it1-1);
			++it1; // 碰到偶数在当前位置插入后,要往后走一步
			//break;
		}
	}
	return 0;
}

比对失效和不失效的情况:

vec.erase(it);
vec1.insert(it1, *it1-1);
// 上面是失效的迭代器,只能执行一次。
// 只要不断更新迭代器迭代器就不会失效
it = vec.erase(it);
it1 = vec1.insert(it1, *it1-1);

自己实现new和delete

  1. malloc和new的区别? a.malloc按字节开辟内存的;new开辟内存时需要指定类型 new int[10],new分配内存是按照类型分配,malloc分配内存按照大小分配 所以malloc开辟内存返回的都是void* operator new -> int*。所以malloc分配后往往要强制转换。 b.malloc只负责开辟空间,new不仅仅有malloc的功能,可以进行数据的初始化 new int(20);<-赋初值 new int[20]; new int[20]();<-数组每个元素赋为0 int() c.malloc开辟内存失败返回nullptr指针;new抛出的是bad_alloc类型的异常 d.new是一个操作符可以重载,而malloc是一个库函数。 e.malloc分配内存时可以通过realloc扩容的。new不行。malloc扩容原理是看下面代码
ptr = malloc(sizeof(int));
ptr1 = realloc(ptr, count * sizeof(int));
if (ptr1 == NULL) // reallocated pointer ptr1
{       
    printf("\nExiting!!");
    free(ptr);
    exit(0);
}
else
{
    ptr = ptr1;           // the reallocation succeeded, we can overwrite our original pointer now
}

realloc实际上是free(ptr)然后重新分配空间。

  1. free和delete的区别? delete: 先调用析构函数, 再free 如果delete后面就是普通指针,如下: delete (int*)pfree(p) 没有区别 new -> operator new delete -> operator delete new和delete能混用吗?=>new[]和delete能混用吗 C++为什么区分单个元素和数组的内存分配和释放呢? new delete new[] delete[] 对于普通的编译器内置类型 new/delete[] new[]/delete => new和delete[] 以及 new[]和delete可以混用 自定义的类类型,有析构函数,为了调用正确的析构函数,那么开辟对象数组的时候,不可以混用。

开辟对象数组new会多开辟4个字节(相比于malloc),记录对象的个数。

下面来通过malloc和free实现new和delete,new[]delete[]

#include <iostream>
using namespace std;

// 如果是对象应该:先调用operator new开辟内存空间、然后调用对象的构造函数(初始化)
void* operator new(size_t size)
{
	void *p = malloc(size);
	if (p == nullptr)
		throw bad_alloc();
	cout << "operator new addr:" << p << endl;
	return p;
}
// delete p;  如果是对象应该:调用p指向对象的析构函数、再调用operator delete释放内存空间
void operator delete(void *ptr)
{
	cout << "operator delete addr:" << ptr << endl;
	free(ptr);
}
// 处理数组的new和delete
void* operator new[](size_t size)
{
	void *p = malloc(size);
	if (p == nullptr)
		throw bad_alloc();
	cout << "operator new[] addr:" << p << endl;
	return p;
}
void operator delete[](void *ptr)
{
	cout << "operator delete[] addr:" << ptr << endl;
	free(ptr);
}

class Test
{
public:
	Test(int data = 10) { cout << "Test()" << endl; }
	~Test() { cout << "~Test()" << endl; }
private:
	int ma;
};
int main()
{
  /*
  通过汇编可以看到,new和delete在汇编指令上表现为:
      call operator new 和 call operator delete
  可以得知new和delete本质上是重载函数的调用
  */
  try
  {
    int *p = new int;
    delete p;

    int *q = new int[10];
    delete []q;
  }
  catch (const bad_alloc &err)
  {
    cerr << err.what() << endl;
  }
  
  
	Test *p1 = new Test();
	delete []p1;

	/*
	operator new[] addr:00940268
	Test()
	Test()
	Test()
	Test()
	Test()
	~Test()
	operator delete addr:0094026C
	*/
	Test *p2 = new Test[5];
	cout << "p2:" << p2 << endl;
	delete[] p2; // Test[0]对象析构, 直接free(p2)
	

  return 0;
}