明明白白c++ effective c++ 条目11-15

effective c++的下载地址

http://download.csdn.net/detail/mlkiller/5335383


条款11: 为需要动态分配内存的类声明一个拷贝构造函数和一个赋值操作符

这个条款的原因在哪里呢?
就是如果你创建一个类,什么都不做,那么类会给你创建一个默认构造函数,默认析构函数,默认拷贝函数和默认赋值函数。

所以出问题就出在默认上面去了,尤其是默认拷贝函数和默认赋值函数出的问题最多。
默认拷贝函数会怎么做呢,对于a=b,它会将b中的成员逐位拷贝给另一个a,如果通过指针动态分配内存,则仅仅将指针的值赋给a。
这会导致至少两个问题:
第一,b曾指向的内存永远不会被删除,因而会永远丢失。这是产生内存泄漏的典型例子。第二,现在a和b包含的指针指向同一个字符串,那么只要其中一个离开了它的生存空间,其析构函数就会删除掉另一个指针还指向的那块内存。
看下面代码:

#include <iostream>
#include <stdlib.h>
#include <string.h>
using namespace std;

class MyString
{
public:
      MyString(const char* value);
	  ~MyString();
      friend ostream& operator << (ostream& os,MyString &c);
private:
	  char *data;

};

MyString::MyString(const char* value)
{
		if(value){
				data = new char[strlen(value)+1];
				strcpy(data,value);
		}
		else{
				data = new char[1];
				data = '\0';
		}
}

ostream& operator << (ostream& os,MyString &c)
{
		os<<c.data;
		return os;
}
MyString::~MyString()
{
		delete []data;
}

int main()
{
		MyString a("hello");
		{
		    MyString b("world");
	            b  = a;
		}
		MyString c = a;
		
		cout<<c<<endl;

}

看到输出的结果是这样的
▒▒#a▒▒#a
Aborted (core dumped)
可以看到c得不到正确的值,同时析构函数调用的时候出现崩溃的问题。

所以,如果你会调用拷贝构造函数和赋值函数,那么一定要显示的声明他们,否则将他们定义为私有的。

那么下面就看看如何显示的声明他们,如下
MyString::MyString()
{
	data = NULL;
}

MyString::MyString(MyString &myString)
{
	if (this == &myString)
	{
		return;
	}
	else
	{
		if(myString.data != NULL)
		{
			data = new char[strlen(myString.data)];
			strcpy(data,myString.data);
		}
		else
		{
			data = new char[1];
			data = '\0';
		}
	}

}

MyString & MyString::operator= (const MyString &myString)
{
	if (this == &myString)
	{
		return *this;
	}
	else
	{
		delete []data;
		if(myString.data != NULL)
		{
			data = new char[strlen(myString.data)];
			strcpy(data,myString.data);
		}
		else
		{
			data = new char[1];
			data = '\0';
		}
	}
	return *this;
}

这里需要注意几点:
1 MyString c = a; 实际上调用的是MyString::MyString(MyString &myString), 而不是重载 =号的函数。
   你可以用vs或者gdb来单步调试。原因在于,这种形式是初始化,而不是赋值。
2 下面两个才是赋值 
             MyString b("world");
     MyString c;
     b  = a;
     c = a;
     这个时候,你需要先删除之前的data数据,考虑到有的时候参数为0,所以就定义一个参数为0的构造函数,或者将上面的构造函数写成MyString::MyString(const char* value = NULL)的缺省参数的形式。
3 拷贝构造函数的时候,不需要删除data,因为拷贝构造函数的时候,this->data肯定没有申请空间,删除会引起错误。
4 上面代码其实有二个错误,就是应该申请的空间为strlen(data)+1,最后一位'\0';
   我在cygwin 下面运行是没问题的,估计是侥幸,但是 vs里面运行就会崩溃。

另外,大家可以练习怎么写这个函数,面试经常会问到的。真正的理解,需要一个过程,主要很多细节:要判断是否相等,要注意什么时候可以删除data,什么时候不可以, 要注意new [] 和delete[] 的对应。

条款12: 尽量使用初始化而不要在构造函数里赋值

书上介绍的很清楚了,我这里主要总结一下、
初始化的好处:
1 const 和引用必须使用初始化,而不能用赋值。
2 效率高。
     初始化的流程,就一步,将类成员初始化。
     而构造函数内赋值则有两步:第一,调用默认构造函数,第二,调用赋值构造函数。
    类的构造函数的本质上是函数,函数就有形参和实参。
   例如
 
   class A
   { 
   pulic:
          A(B &bInput);
   private:
          B b;
  }
  A::A(B &bInput)
  {
       b = bInput;
}

    它的流程是什么呢?先调用B()生成b,然后在调用operator = 赋值函数,效率当然无法保证了。这也是第一条的原因,如果是 const或者引用则等于是声明了一下,而没有初始化,那么编译的时候肯定报错。

条款13: 初始化列表中成员列出的顺序和它们在类中声明的顺序相同

举个简单例子:
#include <iostream>
using namespace std;

class A
{
public:
	A(int value);
	void print();
private:
	int i;
	int j;
};
A::A(int value):j(value),i(j)
{
}
void A::print()
{
		cout<<i<<" "<<j<<endl;
}
int main()
{
   
		A a(10);
		a.print();
}

这个输出的结果是什么?
有些人以为是10 10
我电脑上的结果是
2281060 10
第一个数是随机数,因为执行的顺序是j(i) 然后才是j(10)

条款14: 确定基类有虚析构函数

我之前有篇文章专门将这个,大家可以看看
http://blog.csdn.net/mlkiller/article/details/8884321   
这里就不展开了。

条款15: 让operator=返回*this的引用

刚才在写前面的例子,重载符号<<和=的时候,我还想返回值怎么去写。
<<符号是和=都是二元操作符,但是后面跟的参数不一样,<<跟了两个参数,流对象和操作对象,它最后返回流对象。
而=只有自己,它的返回值呢应该是这个对象本身,我就在纠结&引用怎么返回,它和this指针之间什么关系。
等知道答案的时候,还是有些疑惑*this和&引用相等么?看个例子

int p = 1;
int *q = &p;
int &t = p;
int &s = *q;

你看到引用本身怎么用指针初始化。

关于文中返回const类型,我没有搞得太清楚,时间比较晚了,以后弄明白在写进来。

你可能感兴趣的:(C++,初始化,构造函数,effect,析构函数)