我们平时编写代码都是使用的集成开发工具,很多时候都忽略了c++中隐藏的成员函数。具体来说,c++编译器会自动提供一下几个基本函数:
●默认构造函数,如果没有定义构造函数
●复制构造函数,如果没有定义
●析构函数,如果没有定义
●赋值操作符,如果没有定义
●地址操作符
class Stack { private: char *str; public: Stack(); //默认构造函数 ~Stack();//析构函数 Stack (const Stack &); //复制构造函数 Stack (char *str ) // 构造函数 Stack & Stack::operator=(const Stack &); //赋值操作符 Stack & operator =(const Stack &); //地址操作符 void Add() // 成员函数 };
刚开始学习c++的时候见的比较多的也就默认构造函数和析构函数了,其他的几个很少用到,甚至在代码中没有见到过,今天在这里梳理一下。
1、默认构造函数。如果没有提供任何构造函数,c++将创建一个没有任何参数和行为的默认构造函数。如果定义了构造函数,c++将不会定义默认构造函数。如果希望在创建对象时显式地对它进行初始化,或需要创建对象数组时,则必须显式地定义默认构造函数。这里需要说明一下,在创建对象数组时,这个类必须提供默认构造函数,因为初始化对象数组的方案是,首先使用默认构造函数创建数组元素,然后花括号中的构造函数将创建临时对象,然后将临时对象的内容复制到相应的元素中。
有两种方式可以用来定义默认构造函数,一种是给已有构造函数的所有参数提供默认值:
Stock(const char* a = "Error", int m = 0,double dr = 0.0);
另一种方式是通过函数重载来定义另一个构造函数——一个没有参数的构造函数:
Stock();
由于只能有一个默认构造函数,因此不要同时采用这两种方式。
2、复制构造函数
又叫拷贝构造函数。它用于将一个对象复制到新创建的对象中。也就是说,它用于初始化过程中,而不是常规的赋值过程中。类的复制构造函数原型通常如下:
class_name (const class_name &)
它接受一个指向类对象的常量引用作为参数。
这里需要注意复制构造函数和赋值操作符的区别,以免混淆两者的功能。
1)新建一个对象并将其初始化为同类现有对象时,复制构造函数都将被调用。每当程序生成了对象副本时,编译器都将使用复制构造函数,具体说,当函数按值传递对象或函数返回对象时,都将使用复制构造函数。假设motto是一个Stack类对象,下面几种都会调用复制构造函数。
Stack ditto(motto); Stack ditto = motto; Stack ditto = Stack(motto); Stack *pDitto = new Stack(motto);
2)默认的复制构造函数逐个复制非静态成员(浅复制),复制的是成员的值。如果成员本身是类的对象,则将使用类的复制构造函数复制类的成员对象,静态函数不受影响,因为静态函数属于类本身,不属于任何一个类对象。这里需要注意的问题:对资源的复制。我们需要实现深度复制。比如说,Stack类对象ditto,该对象的str指向一块堆内存。现在利用它初始化另一个对象motto,这里如果简单的对其进行浅复制,则ditto.str和motto.str所保存的内存地址是同一块内存,这显然是不合适的。因此,这里我们应该在复制的同时给motto.str重新分配一块内存,然后将diito.str字符串复制过来,这就实现了深度复制。
Stack::Stack (const Stack & st) { int len = strlen(st.str); str = new char[len + 1]; str[len] = '\0'; strcpy(str,st.str); }
3、析构函数
析构函数用来完成资源清理工作。一个类中只能有一个析构函数,且析构函数也可以没有返回值和声明类型,不带任何参数。
需要注意的是,我们不应该在代码中显式的调用析构函数。如果调用的是静态存储类对象,则其析构函数将在程序结束时自动被调用。如果创建的是自动存储类对象,则其析构函数将在程序执行完代码块时自动被调用。此外,应注意析构函数和构造函数对堆资源的管理。用new申请的应该用delete释放,用new[]申请的应该使用delete[]释放。为了简单,建议都是用new[]和delete[]。
4、赋值操作符
函数原型如下:
Class_name & Class_name::operator= (const Class_name &);
将已有的对象赋值给另外一个对象,将使用重载赋值操作符。
这里应该要弄清楚初始化和赋值的区别,不然不好弄清楚赋值操作符和复制构造函数的区别:赋值操作符所作用的两个对象都是已有的,而复制构造函数所作用的两个对象一个是已有的,一个是全新的。
与复制构造函数类似,赋值操作符的隐式实现也对成员进行逐个复制,如果成员本身就是类对象,则程序将使用为这个类定义的赋值操作符来复制该成员,但静态数据成员不受影响。同时,这里和复制构造函数有一个相似的问题,就是对资源的开辟。
Stack & Stack::operator= (const Stack & st) { if (this == &st) return *this; delete[] str; int len = strlen(st.str); str = new char[len+1]; str[len] = '\0'; strcpy(str,st.str); return *this; }
这里需要注意继承的问题,MSDN上有一句:
All overloaded operators except assignment (operator=) are inherited by derived classes
如果派生类中声明的成员与基类的成员同名,那么,基类的成员会被覆盖或隐藏,哪怕基类的成员与派生类的成员的数据类型和参数个数都完全不同。所以,“赋值运算符重载函数”不是不能被派生类继承,而是被派生类的默认“赋值运算符重载函数”给覆盖了。
附上:重载、覆盖和隐藏的区别?
函数的重载是指C++允许多个同名的函数存在,但同名的各个函数的形参必须有区别:形参的个数不同,或者形参的个数相同,但参数类型有所不同。
覆盖(Override)是指派生类中存在重新定义的函数,其函数名、参数列、返回值类型必须同父类中的相对应被覆盖的函数严格一致,覆盖函数和被覆盖函数只有函数体 (花括号中的部分)不同,当派生类对象调用子类中该同名函数时会自动调用子类中的覆盖版本,而不是父类中的被覆盖函数版本,这种机制就叫做覆盖,但要求有virtual关键字。
隐藏是指派生类的函数屏蔽了与其同名的基类函数,规则如下:
1) 如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键字,基类的函数将被隐藏(注意别与重载混淆)。
2) 如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)。
参考链接:
http://www.cnblogs.com/sujz/archive/2011/05/12/2044365.html