第36课 经典问题解析三
1. 关于赋值的疑问
(1)疑问
①编译器为每个类默认重载了赋值操作符
②默认的赋值操作符仅完成浅拷贝
③当需要进行深拷贝时必须重载赋值操作符
④赋值操作符与拷贝构造函数有相同的存在意义
【编程实验】默认赋值操作符重载 36-1.cpp
#include <iostream> using namespace std; class Test { int* m_pointer; public: Test(){ m_pointer = NULL; } Test(int i){m_pointer = new int(i);} Test(const Test& obj){ m_pointer = new int(*obj.m_pointer);} //赋值4个注意点: //1.返回值为引用。2.参数是const引用的对象 //3.避免自赋值(this!=&obj)。4.返回时*this; Test& operator = (const Test& obj) { if( this != &obj) { delete m_pointer; m_pointer = new int(*obj.m_pointer); } return *this; } void print() { cout << "m_pointer = " << hex << m_pointer << endl; } ~Test(){delete m_pointer;} }; int main() { Test t1 = 1;//Test(int i); Test t2; //Test(); t2 = t1; //operator=,赋值操作符 t1.print(); //t1.m_pointer != t2.m_pointer; t2.print(); // return 0; }
运行结果:
问题分析:
一般性原则:重载赋值操作符,必然需要实现深拷贝!!!
【编程实验】数组类的优化 IntArray
//IntArray.h
#ifndef _INTARRAY_H_ #define _INTARRAY_H_ class IntArray { private: int m_length; int* m_pointer; IntArray(int len); IntArray(const IntArray& obj); bool construct(); public: static IntArray* NewInstance(int length); int length(); bool get(int index, int& value); bool set(int index ,int value); int& operator [] (int index); IntArray& operat or = (const IntArray& obj); IntArray& self(); ~IntArray(); }; #endif
//IntArray.cpp
#include "IntArray.h" IntArray::IntArray(int len) { m_length = len; } bool IntArray::construct() { bool ret = true; m_pointer = new int[m_length]; if( m_pointer ) { for(int i=0; i<m_length; i++) { m_pointer[i] = 0; } } else { ret = false; } return ret; } IntArray* IntArray::NewInstance(int length) { IntArray* ret = new IntArray(length); if( !(ret && ret->construct()) ) { delete ret; ret = 0; } return ret; } int IntArray::length() { return m_length; } bool IntArray::get(int index, int& value) { bool ret = (0 <= index) && (index < length()); if( ret ) { value = m_pointer[index]; } return ret; } bool IntArray::set(int index, int value) { bool ret = (0 <= index) && (index < length()); if( ret ) { m_pointer[index] = value; } return ret; } int& IntArray::operator [] (int index) { return m_pointer[index]; } IntArray& IntArray::operator = (const IntArray& obj) { if( this != &obj ) { int* pointer = new int[obj.m_length]; if( pointer ) { for(int i=0; i<obj.m_length; i++) { pointer[i] = obj.m_pointer[i]; } m_length = obj.m_length; delete[] m_pointer; m_pointer = pointer; } } return *this; } IntArray& IntArray::self() { return *this; } IntArray::~IntArray() { delete[]m_pointer; }
//main.cpp
#include <iostream> #include "IntArray.h" using namespace std; int main() { IntArray* a = IntArray::NewInstance(5); IntArray* b = IntArray::NewInstance(10); if(a && b) { IntArray& array = a->self(); IntArray& brray = b->self(); cout << "array.length() = " << array.length() << endl; //5 cout << "brray.length() = " << brray.length() << endl; //10 array = brray; //赋值 cout << "array.length() = " << array.length() << endl; //10 cout << "brray.length() = " << brray.length() << endl; //10 } delete a; delete b; return 0; }
运行结果:
(2)编译器默认提供的函数
-
不带参的构造函数
-
拷贝构造函数
-
默认的赋值操作符
-
析构函数
(3)一般原则:重载赋值操作符,必然需要实现深拷贝!!!
2. 关于string的疑问
(1)string类内部维护了一个指向数据的char*指针(m_cstr),这里用于存放字符串数据的堆空间地址。因字符串操作(如复制、合并、追加等),
所以这个指针可能在程序运行的过程中发生改变。
【编程实验】字符串问题1 36-2.cpp
#include <iostream> using namespace std; int main() { string s = "12345"; //程序试图在C的方式访问字符串(不建议这样用!) const char* p = s.c_str();//c_str表示C方式的字符串 cout << p << endl; //打印12345 s.append("abcde"); //p成为野指针,因为追加字符串,可能 //导致堆内存的重新分配,从而m_cstr //指的堆内存地址改变了,但p并不知道! cout << p << endl; //仍然指向旧的地址(野指针) return 0; }
运行结果:
问题分析:
(2)string类内部维护了一个m_length的变量,用于指示字符串中字符的个数。当使用C的方式使用string对象时,这个m_length可能不会自动更新。
【编程实验】字符串问题2 36-3.cpp
#include <iostream> using namespace std; int main() { const char* p = "12345"; string s = ""; //s.length==0 //保留一定量内存以容纳一定数量的字符 s.reserve(10);//s.length ==0; //不要使用C语言的方式操作C++中的字符串 for(int i=0; i < 5; i++) { s[i] = p[i];//注意,虽然此时s对象中的字符串内存 //确实被赋新的值了。但用这种方式赋值 //相等于只是通过指针赋值,s.length不 //会自动更新,即仍为0 } cout << s.length() << endl; //0 cout << s.empty() << endl; //1 cout << s << endl; //这里将不会有任何输出,因为s.length=0; return 0; }
运行结果:
问题分析:
3. 小结
(1)在需要进行深拷贝的时候必须重载赋值操作符
(2)赋值操作符和拷贝构造函数有同等重要的意义
(3)string类通过一个数据空间保存字符数据
(4)string类通过一个成员变量保存当前字符串的长度
(5)C++开发时尽量避开C语言中惯用的编程思想