1.完成一个String类,类中只给构造函数和析构函数,拷贝构造函数和赋值运算符重载在使用编译器提供默认的,当调用拷贝构造函数创建对象,或用对象给对象进行赋值,看看会出现什么问题
#include<iostream>
#include<string.h>
class String
{
public:
String(const char* str = "")
{
if (str == NULL)
{
_str = new char[1];
*_str = '\0';
}
else
{
_str = new char[strlen(str) + 1];
strcpy(_str, str);
}
}
~String()
{
if (_str)
{
delete[] _str;
_str = NULL;
}
cout << this << endl;
}
private:
char* _str;
};
int main()
{
String s1("hello");
String s2(s1);
return 0;}
执行程序时会发现代码崩溃,这是为什么呢?后来发现原来这涉及到了浅拷贝。
2. 什么是浅拷贝?有什么危害
上述其实就是一种简单形式的浅拷贝,上述是调用的编译器自己定义的拷贝构造函数,出错原因在于拷贝时,连对象的地址一同拷贝了,导致s1和s2共用了一块地址空间,但是在程序调用结束后,会自动调用析构函数释放资源。这时会按照“先构造的后析构”的顺序进行对象的析构来释放资源,所以先析构s2,这时s2析构完成,代表这块空间已经被释放了,而且指针会被赋成NULL,但是系统还会按照顺序去析构s1,这时s1和s2指向的是同一块地址空间,可是这块地址空间已经被释放,不存在资源了,再次释放就会程序崩溃了。
3. 解决浅拷贝方式一:普通版深拷贝
#include<iostream>
using namespace std;
#include<string.h>
class String
{
public:
String(const char* str = "")
{
if (str == NULL)
{
_str = new char[1];
*_str = '\0';
}
else
{
_str = new char[strlen(str) + 1];
strcpy(_str, str);
}
}
String(const String& s)
: _str(new char[strlen(s._str) + 1]) //为新创建的对象开辟了自己的空间
{
strcpy(_str, s._str);
}
/*String& operator=(const String& s) 申请空间失败的话,原来的_str的地址就找不到了
{
if (this != &s)
{
_str = new char[strlen(s._str) + 1];
strcpy(_str, s._str);
}
return *this;
}*/
String& operator=(const String& s) //上述的改进版
{
if (this != &s)
{
char *tmp = new char[strlen(s._str) + 1];
strcpy(tmp, s._str);
delete[]_str;
_str = tmp;
}
return *this;
}
~String()
{
if (_str)
{
delete[] _str;
_str = NULL;
}
}
private:
char* _str;
};
int main()
{
String s1("hello");
String s2(s1);
String s3;
s3 = s1;
return 0;
}
4. 解决浅拷贝方式二:简介版的深拷贝
String(const String& s)
{
String tmp(s._str);
swap(_str, tmp._str);
}
这个代码在运行时会发生程序崩溃的情况,为什么呢???
改进版:
String(const String& s)
:_str(NULL)
{
String tmp(s._str);
swap(_str, tmp._str);
}
String& operator=(const String& s) //这时不需要给_str赋值,是因为在main中已经通过构造函数创建好了
{
if (this != &s)
{
String tmp(s._str);
swap(_str, tmp._str);
}
return *this;
}
5. 解决浅拷贝方式三:引用计数实现(计数该怎么给:普通成员变量,静态成员变量为什么都不行,分析出原因,指针)---注意:仍旧是浅拷贝
普通成员变量
class String
{
public:
String(const char* str = "")
{
if (str == NULL)
{
_str = new char[1];
_count = 1;
*_str = '\0';
}
else
{
_str = new char[strlen(str) + 1];
_count = 1;
strcpy(_str, str);
}
}
String(String& s)
{
_str = s._str;
s._count++;
_count = s._count;
}
String& operator=(String& s)
{
if (this != &s)
{
_str = s._str;
s._count++;
_count = s._count;
}
return *this;
}
~String()
{
if (_str)
{
if ((--_count)==0)
{
delete[] _str;
_str = NULL;
}
}
}
private:
char* _str;
int _count;
};
int main()
{
String s1("hello");
String s2(s1);
String s3;
s3=s1;
return 0;
}
运行结果(没有析构前)
每个变量里面都有各自的_count的计数,可以发现虽然s2._str和s1._str和s3._str都一样,但是计数却是2,原因在于他们各自_count的存储位置是不一样的,所以析构就会出现如下问题
每个对象的计数都各自减1,导致每个对象的资源都没有被释放。
那么用静态变量呢????????????????
class String
{
public:
String(const char* str = "")
{
if (str == NULL)
{
_str = new char[1];
_count = 1;
*_str = '\0';
}
else
{
_str = new char[strlen(str) + 1];
_count = 1;
strcpy(_str, str);
}
}
String(String& s)
{
_str = s._str;
_count++;
}
String& operator=(String& s)
{
if (this != &s)
{
_str = s._str;
_count++;
}
return *this;
}
~String()
{
if (_str)
{
if ((--_count) == 0)
{
delete[] _str;
_str = NULL;
}
}
}
private:
char* _str;
static int _count;
};
int String::_count = 0;
int main()
{
String s1("hello");
String s2(s1);
String s3;
s3 = s1;
return 0;
}
运行结果
本应_count为3,为什么是2呢??
这是因为_count是静态变量,每次调用构造函数创造新变量的时候就会制成1,这就导致相同的空间_count并没有加1,析构的时候s2会提前释放,若是再创建一个s4,这时释放完s4,就结束了,s1,s2,s3,还没有释放。
指针
class String
{
public:
String(const char* str = "")
{
if (str == NULL)
{
_str = new char[1];
_count = new int[1];
*_str = '\0';
*_count = 1;
}
else
{
_str = new char[strlen(str) + 1];
_count = new int[1];
*_count = 1;
strcpy(_str, str);
}
}
String(String& s)
/*:_count(s._count)*/
{
_str = s._str;
_count = s._count;
(*_count)++;
}
String& operator=(String& s){
if (this != &s)
{
_str = s._str;
_count = s._count;
(*_count)++;
}
return *this;
}
~String()
{
if (_str)
{
if ((--(*_count)) == 0)
{
delete[] _str;
delete[]_count;
_str = NULL;
_count = NULL;
}
}
}
private:
char* _str;
int *_count;
};
int main()
{
String s1("hello");
String s2(s1);
String s3;
s3 = s1;
return 0;
}
还存在一个问题就是改变其中一个对象的值所有与之共用空间的对象都要被改变。如下面的程序。
class String
{
public:
String(const char* str = "")
{
if (str == NULL)
{
_str = new char[1];
_count = new int[1];
*_str = '\0';
*_count = 1;
}
else
{
_str = new char[strlen(str) + 1];
_count = new int[1];
*_count = 1;
strcpy(_str, str);
}
}
String(String& s)
/*:_count(s._count)*/
{
_str = s._str;
_count = s._count;
(*_count)++;
}
{
if (this != &s)
{
_str = s._str;
_count = s._count;
(*_count)++;
}
return *this;
}
char& operator[](int index)
{
assert(index >= 0 && index < strlen(_str));
return _str[index];
}
~String()
{
if (_str)
{
if ((--(*_count)) == 0)
{
delete[] _str;
delete[]_count;
_str = NULL;
_count = NULL;
}
}
}
private:
char* _str;
int *_count;
};
int main()
{
String s1("hello");
String s2(s1);
String s3;
s3 = s1;
s3[1] = 'a';
return 0;
}
给[]的重载运算符做下修改,如下
char& operator[](int index)
{
assert(index >= 0 && index < strlen(_str));
if ((*_count)>1)
{
--(*_count);
char *tmp1 = new char[strlen(_str) + 1];
strcpy(tmp1, _str);
int *tmp2 = new int[1];
*tmp2 = 1;
_str = tmp1;
_count = tmp2;
}
return _str[index];
}
上述的值可以进行修改了,空间也可以被准确释放了,但是对于这种情况,每次创建对象时,都需要另外的再开辟一个整形的空间去存放_count,会造成空间的浪费,如果你忘了释放这段空间,得不偿失。
6.所以再做下修改-------->写时拷贝
#if 1
class String
{
public:
String(const char* str = "")
{
if (str == NULL)
{
_str = new char[5];
*(int*)_str = 1;
_str += 4;
*(_str) = '\0';
}
else
{
_str = new char[strlen(str) + 1+4];
*(int*)_str = 1;
_str += 4;
strcpy(_str, str);
}
}
String(String& s)
:_str(s._str)
{
++(*(int *)(_str-4));
}
String& operator=(String& s)
{
if (this != &s)
{
_str = s._str;
++(*(int *)(_str - 4));
}
return *this;
}
char& operator[](int index)
{
assert(index >= 0 && index < strlen(_str));
if ((*(int *)(_str - 4))>1)
{
--(*(int *)(_str - 4));
char *tmp = new char[strlen(_str) + 1+4];
tmp += 4;
strcpy(tmp, _str);
_str = tmp;
*(int *)(_str - 4) = 1;
}
return _str[index];
}
~String()
{
if (_str)
{
if ((--(*(int *)(_str - 4))) == 0)
{
delete[] (_str-4);
_str = NULL;
}
}
}
private:
char* _str;
};
#endif
int main()
{
String s1("hello");
String s2(s1);
String s3;
s3 = s1;
s3[1] = 'a';
return 0;
}
节省了空间。。。。
7. 最后实现的String类存在线程安全问题。为什么存在线程安全问题?
如果在多线程中,多个线程同时访问String类,这时就会出现数据在多个线程里被更改,可能会发生空间数据错乱的情况,可以根据操作系统的加锁机制,当一个进程在访问String类时,就上锁,以防其他进程进入。当该进程访问结束时,就开锁,这时其他进程就可以访问了,之于访问的顺序,由于有很多算法提供在此不一一说明了,详情可参考操作系统的书。
8. 查看vs平台下,String类的实现原理,是深拷贝还是COW(copy on write 即写时拷贝)
由此看来在VS下是COW
9. 在上述两个String类任意一个,实现一个完整的string,注意:不能调用C库中的字符串函数
class String
{
public:
String(const char* str = "")
{
if (str == NULL)
{
_str = new char[1];
*_str = '\0';
}
else
{
_str = new char[strlen(str) + 1];
strcpy(_str, str);
}
}
size_t Size()const
{
return strlen(_str);
}
bool operator>(const String& s)
{
while (*_str != '\0')
{
if ((*_str) > (*s._str))
return true;
_str++;
}
return false;
}
bool operator<(const String& s)
{
while (*_str != '\0')
{
if ((*_str) < (*s._str))
return true;
_str++;
}
return false;
}
bool operator==(const String& s)
{
while (*_str != '\0')
{
if ((*_str) == (*s._str))
return true;
_str++;
}
return false;
}
String operator+(const String& s)
{
assert(_str, s._str);
char *tmp = new char[strlen(_str) + strlen(s._str) + 1];
strcpy(tmp, _str);
strcat(tmp, s._str);
_str=tmp;
return _str;
}
size_t StrStr(const String& s)
{
int count = 0;
assert(_str, s._str);
char *s1 = _str;
char *s2 = s._str;
while (*s1&&*s2)
{
s1++;
if (*s1 == *s2)
{
s2++;
count++;
}
}
return count;
}
String(const String& s)
: _str(new char[strlen(s._str) + 1]) //为新创建的对象开辟了自己的空间
{
strcpy(_str, s._str);
}
String& operator=(const String& s) //上述的改进版
{
if (this != &s)
{
char *tmp = new char[strlen(s._str) + 1];
strcpy(tmp, s._str);
delete[]_str;
_str = tmp;
}
return *this;
}
~String()
{
if (_str)
{
delete[] _str;
_str = NULL;
}
}
private:
char* _str;
};
int main()
{
size_t size;
String s1("hello");
String s2(" bitgg");
/*size_t size;
size=s1.Size();
String s2(" bit");
bool s;
s = (s1 > s2);
s = (s1 == s2);
s = (s1 < s2);*/
String s3;
s3=s1 + s2;
String s4("bit");
size = s3.StrStr(s4);
return 0;
}