Class的两个经典分类
- Class without pointer member(s)
complex - Class with pointer member(s)
string
String class
以下为头文件string.h内容
#ifndef _MYSTRING_
#define _MYSTRING_
//类中进行字符串的构造
class String
{
...
}; //谨记切勿忘分号
//字符串所具备的功能
String::function(...) ...Global-function(...) ...#endif
应用拷贝构造和拷贝赋值
以下为string-test.cpp
int main()
{ String s1();String s2("hello");//拷贝构造,以s1为初值设置到s3上String s3(s1);cout << s3 <<endl;//拷贝赋值,s2赋值给s3s3 = s2;cout << s3 <<endl;
}
Note
- 对于用class with pointer member(s)这种编译器给的一套拷贝构造和拷贝赋值,会有不好的影响,假如现在有一个对象,它里面有一个指针,指向另外一个地方,我现在又产生新的对象,如果只是拷贝,那就是指针拷贝过来,它们两个指到同一个地方去,所以只要class里边带指针,一定不能用编译器给的默认版本,一定要自己写
Big Three三个特殊函数
class String
{public://Big Three,三个特殊函数String(const String& str); //拷贝构造String& operator=(const String& str); //拷贝赋值~String(); //析构
};
class String的具体内容
class String
{ //只要类带指针,就一定要写出拷贝构造和拷贝赋值public:String(const char* cstr = 0 );//接收自己的引用String& ,拷贝构造String(const String& str);//操作符=重载(拷贝赋值动作)String& operator=(const String& str);//析构函数//当这个类的对象死亡的时候,就会调用析构函数~String();//一般成员函数,这个函数为内联函数char* get_c_str() const { return m_data; }//返回指针,并没有改变数据m_data的value所以函数要加cosntprivate:char* m_data;
};//谨记切勿忘分号
一般而言,都会对字符串作出以下设计
==让字符串里有一个指针,==在需要内存的时候,才创建另一个空间(例下面的hello所占的空间)去放字符本身,字符本身有大有小(hello有5个字符,helloworld有10个字符)有时候是空字符串,这样的设计有动态的感觉.而不要在class的private中放一个数组,那这个数组要设定多大呢?10000?300?所以需要一种动态分配的方式,即指针。不能用编译器给我们的拷贝构造、拷贝赋值,要自己写一套(例如上述代码public: String(const…)…的构造函数)
ctor和dtor(构造函数和析构函数)
- 字符串是什么,就是指针指着头,后面一串,最后有一个结束符\0
//字符串构造函数
inline
String::String(const char* cstr = 0)
{ //检查传进来的字符是0吗?if(cstr){ //传入的字符非空,即指定了初值。有[中括号]的叫array newm_data = new char[strlen(cstr)+1];//动态分配空间,+1存放结束符strcpy(m_data, cstr);}else {//传入的字符为空,即未指定初值m_data = new char[1];//new分配一块内存,分配1个字符*m_data = '\0';// 结束符\0}
}
//构造析构函数,作用关门清理,释放动态分配的内存,防止内存泄漏
inline
String::~String()
{ //array new 要搭配 array delete不然会出错delete[] m_data;//有[中括号]的这种叫array delete
}
应用
- 以下程序调用三次构造函数,三次析构函数
{String s1();String s2("hello");//m_data = new char[strlen(cstr)+1]String* p = new String("hello");//为hello动态分配6个字符的内存(包括结束符)delete p;
}
array new 一定要搭配 array delete
- array new (本例中 m_data = new char[strlen(cstr)+1] ; )
- array delete(本例中delete[] m_data;//[ ] 表明要删除的是数组,而不是一个字符,唤起3次析构函数,把动态分配的内存删掉)
class with pointer members必须有copy ctor和copy op
- 以下为浅拷贝:默认拷贝构造函数将会拷贝指针,会造成内存泄漏(memory leak)
将对象a的内容拷贝到对象b,对象b的指针由原来指向W变成现在指向H,所以现在没有指针指向W,造成了内存泄漏
String a("Hellio");
String b("World");
b = a;//这里是浅拷贝,对象b的指针指向H,但没有指针指向W,造成了内存泄漏
//什么是深拷贝呢?就是我们要写的copy ctor拷贝构造
copy ctor(拷贝构造函数)
以下程序strcpy为深拷贝:自定义拷贝构造函数将会拷贝对象的内容
inline
String::String(const String& str)
{ //str.m_data直接取另一个对象的private data(兄弟之间互为friend)m_data = new char[ strlen(str.m_data) + 1];strcpy(m_data,str.m_data);//深拷贝,把对象str内容拷贝到m_data
}
应用
{String s1("hello");String s2(s1);//以s1为初值创建出s2String s2 = s1;//意思与上一行完全相同,把s1赋值到s2上,注意s2是新创建的对象,所以需要调用构造函数
}
copy assignment operator(拷贝赋值函数)
- a=b,对象a和b中有内容,将b的内容赋值到a之前
[1]应该先把a中的内容删除
[2]然后创建出与右边一样大的空间
[3]再把右边赋值到左边
inline //成员函数,参数内隐藏this
String& String::operator = (const String& str)//重载操作符=即赋值
{ //this对应下面应用中s2=s1的s2,str对应s1if (this == &str)//检测是否为自我赋值(self assignment)看这两个指针是否指的同一个东西return *this;//如果检测结果是自我赋值,则不进行以下步骤delete[] m_data;//该操作对应上面描述文字[1]m_data = new char[ strlen(str.m_data) + 1 ];//该操作对应上面描述文字[2],+1放置结束符strcpy(m_data, str.m_data);//该操作对应上面描述文字[3]return *this;
}
应用
{String s1("hello");String s2(s1);//赋值操作s2 = s1;
}
以下为a=b赋值过程
第一步:
delete[] m_data;
第二步:
m_data = new char[ strlen(str.m_data) + 1 ]
第三步:
深拷贝
strcpy(m_data, str.m_data);
一定要在operator=中检查是否self assignment
- 写检查自我赋值不只是为了效率,也是为了正确性
[???处含义为将左边对象内容删除]
笔记来源:
https://www.bilibili.com/video/BV1aW411H7Xa?p=7