-
默认构造函数:
- 如果未提供任何构造函数,c++会自动生成默认构造i函数。创建对象时会调用。
默认样式:
className() { }//初始化值随机
- 若想创建对象时不显式的对它进行初始化,则必须显式的定义默认构造函数:
//例如: Klunk::Klunk() { klunk_ct=0;//可以用其设置特定的值 ... }
-
带参的构造函数也可以是默认构造函数,只要所有参数都有默认值:
Klunk(int n=0) { klunk_ct=n; }
- 只能有一个默认构造函数,否则会引发二义性
例子:
String::String() { len=0; str=new char[l];//与下面析构函数相匹配 str[0]='\0';//default string }
- 析构函数中包含如下代码:
delete [] str;
- delete[]与使⽤new[]初始化的指针和空指针都兼容。
String::String() { len=0; //str=new char[l];//与下面析构函数相匹配 //str[0]='\0'; //更改为 str=0; //default string }
-
默认析构函数:
-
复制构造函数:
-
复制构造函数⽤于将⼀个对象复制到新创建的对象中。⽤于初始化过程中(包括按值传递参数)。
-
原型:
Class_name(const Class_name&);
它接受⼀个指向类对象的常量引⽤作为参数。
例如:StringBad(const StringBad&);
-
新建⼀个对象并将其初始化为同类现有对象时,复制构造函数都将被调⽤。最常⻅的情况是将新对象显式地 初始化为现有的对象。
例子:
StringBad ditto(motto); StringBad metto=motto; StringBad also=StringBad(motto); StringBad * pStringBad=new StringBad(motto);
- 其中中间的2种声明可能会使⽤复制构造函数直接创建metoo和 also,也可能使⽤复制构造函数⽣成⼀个临时对象,然后将临时对象的 内容赋给metoo和also,这取决于具体的实现。
- 最后⼀种声明使⽤motto 初始化⼀个匿名对象,并将新对象的地址赋给pstring指针。
-
每当程序⽣成了对象副本时,编译器都将使⽤复制构造函数。
- 当函数按值传递对象(如程序清单12.3中的callme2())或函数返回对象时,都将使⽤复制构造函数。
- 按值传递意味着创建原始变量的⼀个副本。编译器⽣成临时对象时,也将使⽤复制构造函数。
-
由于按值传递对象将调⽤复制构造函数,因此应该按引⽤传递对象。这样可以节省调⽤构造函数的时间以及存储新对象的空间。
复制构造函数的功能:
- 默认的复制构造函数逐个复制⾮静态成员(成员复制也称为浅复制),复制的是成员的值。
- 静态函数(如num_strings)不受影响,因为它们**属于整个类,**⽽不是各个对象。
引例:
-
让程序准确地记录对象计数。解决办法是提供⼀个对计数进⾏更新的显式复制构造函数:
StringBad::StringBad(const StringBad&) { num_strings++; ... }
- 如果类中包含用于记录对象数的静态成员,且其值会在新对象被创建时发生变化,则应提供一个显式复制构造函数来处理计数问题。
-
隐式复制构造函数是按值进⾏复制的。
-
隐式复制构造函数的功能相当于:
sailor.str=sport.str; //复制的是指向字符串的指针,而不是字符串本身。 //当调用析构函数时将产生问题,可能对同一块内存区域进行两次删除,这将导致程序异常终止。
-
解决方案:定义一个显式复制构造函数。
-
解决类设计中这种问题的⽅法是进⾏深度复制(deep copy)。
-
复制构造函数应当复制字符串并将副本的地址赋给str成员,⽽不 仅仅是复制字符串地址。
函数实现:
StringBad::StringBad(const StringBad& st) { num_strings++; len=st.len; str=new char[len+1]; std::strcpy(str,st.str); cout<<num_strings<<":\"<<str<<"\"object created"\n"; ... }
必须定义复制构造函数的原因在于,⼀些类成员是使⽤new初始化的、指向数据的指针,⽽不是数据本⾝。
-
如果类中包含了使⽤new初始化的指针成员,应当定义⼀个复制构造函数,以复制指向的数 据,⽽不是指针,这被称为深度复制。复制的另⼀种形式(成员复制或浅复制)只是复制指针 值。浅复制仅浅浅地复制指针信息,⽽不会深⼊“挖掘”以复制指针引⽤的结构。
-
-
-
-
赋值运算符:
ANSI C允许结构赋值,⽽C++允许类对象赋值,这是通过⾃动为类重载赋值运算符实现的。
-
原型:
Class_name & Class_name::operator=(const Class_name &); //它接受并返回⼀个指向类对象的引⽤ //例子: StringBad & StringBad::operator=(const StringBad &);
赋值运算符的功能及何时调用:
- 将已有的对象赋给另⼀个对象时,将使⽤重载的赋值运算符:
StringBad headline1("Celery Stalks at Midnight"); ... StringBad knot; knot=headline1;
- 初始化对象时,并不⼀定会使⽤赋值运算符。
StringBad metoo=knot;
metoo是⼀个新创建的对象,被初始化为knot的值,因此使⽤复制构造函数。
实现时也可能分两步来处理这条语句:
使⽤复制构造函数创建⼀个临时对象,然后通过赋值将临时对象的值复制到新对象中。
初始化总是会调⽤复制构造函数, ⽽使⽤=运算符时也可能调⽤赋值运算符。
- 赋值运算符的隐式实现也对成员进⾏逐个复制。浅复制将导致相同地址重复删除,造成数据受损
- 如果操作结果是不确定的,则执⾏的操作将随编译器⽽异,包括显⽰独⽴声明 (Declaration of Independence)或释放隐藏⽂件占⽤的硬盘空间。当 然,编译器开发⼈员通常不会花时间添加这样的⾏为。
- 如果成员本⾝就是类对象,则程序将使⽤为这个类定义的赋值运算符来复制该成员,但静态数据成员不受影响。
解决赋值的问题:
解决办法是提供赋值运算符(进⾏深度复制)定义。
- 由于⽬标对象可能引⽤了以前分配的数据,所以函数应使⽤**delete[ ]**来释放这些数据。(即程序运行后,该目标对象将不再指向此内存位置,这将导致内存浪费。)
- 函数应当避免将对象赋给⾃⾝;否则,给对象重新赋值前,释放内 存操作可能删除对象的内容。
- 函数返回⼀个指向调⽤对象的引⽤。
- 通过返回⼀个对象,函数可以像常规赋值操作那样,连续进⾏赋 值,即如果S0、S1和S2都是StringBad对象,则可以编写这样的代码:
S0=S1=S2; //使用函数表示法时,转换为; S0.operator=(S1.operator=(S2)); //,S1.operator=(S2)的返回值是函数S0.operator=()的参数。 //返回值是⼀个指向StringBad对象的引⽤,因此参数类型是正确的。
例子:为StringBad类编写赋值运算符:
StringBad & StringBad::operator=(const StringBad & st) { if(this==&st) return *this; delete [] str; len=st.len; str=new char[len+1]; std::strcpy(str,st.str); return *this; }
- 代码⾸先检查⾃我复制,这是通过查看赋值运算符右边的地址 (&s)是否与接收对象(this)的地址相同来完成的。如果相同,程序 将返回*this,然后结束。
- 如果地址不同,函数将释放str指向的内存,这是因为稍后将把⼀个 新字符串的地址赋给str。如果不⾸先使⽤delete运算符,则上述字符串将保留在内存中。由于程序中不再包含指向该字符串的指针,因此这些内存被浪费掉。
- 接下来为新字符串分配⾜够的内存 空间,然后将赋值运算符右边的对象中的字符串复制到新的内存单元中。
- 程序返回*this并结束。
- 赋值操作并不创建新的对象,因此不需要调整静态数据成员 num_strings的值。
-
-
地址运算符: