本篇文章内容包含:c风格字符串原理,拷贝构造,拷贝赋值,内存分配,内存释放与析构函数,内存空间分类。
代码如下:
1 class SString 2 { 3 public: 4 SString(const char* p=0); 5 SString(const SString& str); 6 SString& operator=(const SString& str); 7 ~SString(){delete[] p_data;} 8 char* get_p_data() const {return p_data;} 9 10 private: 11 char* p_data; 12 }; 13 14 inline SString::SString(const char* p) 15 { 16 if(p) 17 { 18 p_data=new char[strlen(p)+1]; 19 strcpy(p_data,p); 20 } 21 else 22 { 23 p_data=new char[1]; 24 p_data[0]='\0'; 25 } 26 } 27 28 inline SString::SString(const SString& str) 29 { 30 if(str.p_data) 31 { 32 p_data=new char[strlen(str.p_data)+1]; 33 strcpy(p_data,str.p_data); 34 } 35 else 36 { 37 p_data=new char[1]; 38 p_data[0]='\0'; 39 } 40 } 41 42 inline SString& SString::operator=(const SString& str) 43 { 44 if(this==&str) //自我赋值检测 45 { 46 return *this; 47 } 48 delete[] p_data; 49 p_data=new char[strlen(str.p_data)+1]; 50 strcpy(p_data,str.p_data); 51 return *this; 52 } 53 54 inline ostream& operator<<(ostream& os,const SString& str) 55 { 56 cout<<str.get_p_data(); 57 return os; 58 }
一.c风格字符串:
char *p=”sdfsadf”;
为何一个字符串常量可以赋值给一个指针?
双引号做了3件事:
1.申请了空间(在常量区),存放了字符串 。
2. 在字符串尾加上了'/0'。
3.返回地址。
三大函数:拷贝构造,拷贝赋值,析构函数
二.拷贝构造函数:符合构造函数的所有特性,只不过形参类型为本类类型。
拷贝构造函数的调用:
string s1=s2; string s1(s2); //两者意义相同,因为他们都是在创建的时候初始化。都是调用拷贝构造函数。
如果类里面有指针,默认的拷贝构造函数是指针的拷贝。如果用指针的赋值,就是浅拷贝,两个指针指向同一块内存地址。试想一下,如果此时一个指针释放了这块空间,那么另一个指针就成了空指针了。由于预期是深拷贝,所以不能使用默认的拷贝构造函数。
此时应该给被赋值的对象分配一定的内存空间,存放赋值过来的字符串。如果用静态分配的方法,由于不知道每次赋值过来的字符串的长度,因此静态分配不好。此时只能使用动态分配的方法。
三:动态内存分配:
动态分配内存可完全不止成员变量哦,它会定义cookie,来标志分配了多大内存,还会进行填充,以达到16字节的倍数。在vc用new分配的内存如下图:
其中0x00000041就是cookie,41中的4表示分配了64个字节,1表示该内存空间已经分配给用户了。
动态分配的步骤:
例如:
complex *pc=new complex(1,2);
编译器将这个语句转化为三个步骤:
void mem=operator new(sizeof(complex)); //动态分配内存 pc=static_cast<complex*>(men); //指针类型转化 pc->complex::complex(1,2); //调用构造函数
使用new关键字在堆里面开辟内存空间。使被赋值的对象的指针成员指向这块内存空间。最后要注意,在进行内存回收时,由于new出来的内存不属于对象成员,它只是成员指向的空间。因此,在析构函数内,也就是成员指针消亡前,一定要delete掉这块动态分配的空间。不然,析构函数会将指向它的指针成员删掉,这块动态分配的内存空间就成了一个孤儿,它会在程序运行期间一直占用内存,成为内存泄漏。
四.释放动态空间和析构函数:
如图:
例如:
delete ps;
编译器转化为两步:
string::~string(ps);
operator delete(ps);
第一条语句:调用析构函数,释放掉ps所指向的动态分配得到的string对象动态分配的一个数组。由于析构函数为:
~SString(){delete[] p_data;}
即为释放动态分配的数组。
第二条语句:
这一句就是删掉动态分配的complex对象,这个对象里面其实就只有一个动态分配的指针。
注意:
释放动态空间时,若是释放一个string类型的动态数组,注意string类型也会动态分配一个动态数组。
例如:若是创建一个string类型的动态数组:
string *p=new string[3];
在释放时,一定要:
delete[] p;
这样他会唤起三次析构函数。析构掉三个动态分配的char字符串。
否则,调用:
delete p;
只会唤起一次析构函数,只能析构一个动态分配的char字符串。还有两个char字符串成为孤儿。
五.拷贝赋值函数:
核心步骤:由于新旧内存要求的空间长度不一样。因此,要删掉旧空间,创建新空间。
注意:拷贝赋值函数一定要进行自我赋值检测,因为存在自己给自己赋值的可能。如果这样的话,那么下面delete的操作会将动态分配的空间全给释放,这样两边都只剩一个指针,根本就不能进行深拷贝。
六.内存空间分类:
栈:在作用域内,存储变量,然后作用域结束变量消亡。一般变量都在栈里面。
堆:全局内存空间,动态分配。
静态存储区:在作用域结束之后仍然存在,直到整个程序结束。存放静态变量,全局变量。
Static局部变量一定要在能够回到该区域时定义,要不然,回不去的话定义也没意义。