标签:
Q1. 下列运算符,在C++语言中不能重载的是( )。
A. * B. ?: C. :: D. delete
Q2. 编写类String的构造函数、析构函数和赋值函数。
Q3. 复制构造函数与赋值运算符的区别是什么?
Q4. 下述代码的输出结果是什么?
1 #include <iostream> 2 using namespace std; 3 class X 4 { 5 public: 6 X() { cout << "constructor" << endl; } 7 static void * operator new (size_t size) 8 { 9 cout << "new" << endl; 10 return ::operator new(size); 11 } 12 static void operator delete (void * pointee) 13 { 14 cout << "delete" << endl; 15 ::operator delete(pointee); 16 } 17 ~X() { cout << "destructor" << endl; } 18 }; 19 int main() 20 { 21 X * px = new X(); 22 delete px; 23 return 0; 24 }
Q5. 如何限制栈对象的生成?如何限制堆对象的生成?
操作符重载函数的名字为operator后跟着所定义的操作符的符号。像任何其他函数一样,操作符重载函数有一个返回值和一个形参表。形参表必须具有与该操作符数目相同的形参(如果操作符是一个类成员,则包括隐式this形参)。比如赋值是二元运算,所以该操作符函数有两个形参:第一个形参对应着左操作数,第二个形参对应右操作数。
大多数操作符可以定义为成员函数或非成员函数。当操作符为成员函数时,它的第一个操作数隐式绑定到this指针。有些操作符(包括赋值操作符)必须是类的成员函数。比如赋值就必须是类的成员,所以this绑定到指向左操作数的指针。因此,赋值操作符接受单个形参,且该形参是同一类类型的对象。右操作数一般作为const引用传递。
1. 可重载的操作符
大部分操作符是可以重载的,但不是所有的操作符都是可重载的。不能重载的操作符有以下四个:
:: .* . ?:
简单的总结:带"点"的都不能重载。
2. 赋值操作符重载
【示例】已知String类的原型如下,编写类String的构造函数、析构函数和赋值函数。
1 class String 2 { 3 public: 4 // 普通构造函数 5 String(const char *str = NULL); 6 // 拷贝构造函数 7 String(const String &other); 8 // 析构函数 9 ~String(); 10 // 赋值函数 11 String & operator = (const String &other); 12 private: 13 // 用于保存字符串 14 char *m_data; 15 };
(1) String 的析构函数
为防止内存泄漏,我们需要定义一个析构函数。当一个String对象超出它的作用域时,这个析构函数将会释放它所占用的内存。代码如下:
1 String::~String() 2 { 3 delete [] m_data; 4 // 由于m_data是内部数据类型,也可以写成delete m_data 5 }
(2) String的构造函数
这个构造函数可以帮助我们根据一个字符串常量创建一个String对象。这个构造函数首先分配了足量的内存,然后把这个字符串常量复制到这块内存,代码如下:
1 String::String(const char *str) 2 { 3 if (str == NULL) // 若能加上NULL判断则更好 4 { 5 m_data = new char [1]; 6 } 7 else 8 { 9 int length = strlen(str); 10 m_data = new char [length + 1]; // 思考为什么要加1 11 strcpy(m_data, str); 12 } 13 }
strlen函数返回这个字符串常量的实际字符数(不包括NULL终止符),然后把这个字符串常量的所有字符赋值到我们在String对象创建过程中为m_data数据成员新分配的内存中。有了这个构造函数后,我们可以像下面这样根据一个字符串常量创建一个新的String对象:
String str("hello");
(3) String的拷贝构造函数
所有需要分配系统资源的用户定义的类型都需要一个拷贝构造函数。拷贝构造函数还可以帮助我们在函数调用中以传值的方式传递一个String参数,并且当一个函数以值的形式返回String对象时实现"返回时复制"。
1 String::String(const String &other) 2 { 3 if (other.m_data == NULL) 4 { 5 m_data = new char [1]; 6 } 7 else 8 { 9 int length = strlen(other.m_data); 10 m_data = new char [length + 1]; 11 strcpy(m_data, str); 12 } 13 }
(4) String的赋值函数
赋值函数代码如下所示:
String & String::operator = (const String & other) { if (this == &other) // 检查自赋值 return *this; delete [] m_data; // 释放原有的内存资源 int length = strlen(other.m_data); m_data = new char [length + 1]; // 分配新的内存资源 strcpy(m_data, other.m_data); return *this; }
赋值操作符重载需要注意:
A. 是否把返回值的类型声明为该类型的引用,并在函数结束前返回实例自身的引用。只有这样,才可以允许连续赋值。否则如果函数的返回值是void,假设有三个String的对象,str1、str2和str3,在程序中语句str1 = str2 = str3将不能通过编译。
B. 是否把传入的参数的类型声明为常量引用。如果传入的参数不是引用而是实例,那么从形参到实参会调用一次拷贝构造函数。把参数声明为引用可以避免这样的无谓消耗,能提高代码的效率。同时,我们在赋值运算符函数内是不会改变传入的实例的状态,因此应该为传入的引用参数加上const关键字。
C. 是否记得释放实例自身有的内存。如果忘了在分配新的内存之前释放自身已有的空间,将出现内存泄露。
D. 是否判断传入的参数和当前的实例(*this)是不是同一个实例。如果是同一个,则不进行赋值操作,直接返回。如果事先不判断,就进行赋值,那么在释放实例自身的内存时就会导致严重的问题:当*this和传入的参数是同一个实例,那么一旦释放了自身的内存,传入的参数的内存也同时被释放了,因此再也找不到需要赋值的内容了。
3. 输出操作符<<的重载
4. operator new 与 operator delete 的重载
标签:
原文地址:http://www.cnblogs.com/xiaoxxmu/p/5759110.html