今天考完六级了,就来写一写我的第一篇blog吧,说是blog其实叫学习笔记其实更为妥当吧。那么今天就探讨一些关于constructor以及一些c++中的特殊函数吧。
我是从java来入门的ob,就对人类的友好型来说自然是Java要好许多,每一个都需要一个初始化的方法,这是需要用一种叫constructor(构造器又叫构造函数),构造函数采用与类相同的名字,构造器方法是可以重载的,在Java中构造器方法重载还可以用this关键字来在构造器中调用构造器,在继承中则可已在子类中使用super关键字来继承父类的构造器。
而在C++中则要麻烦的多,首先C++需要许多类型的特殊函数,除了构造函数以外还需要关注复制构造函数以及复制运算符,而且在C++11中还提供了移动构造函数(move constructor)和移动赋值运算符(move assignment operator)。
统一的构造函数已然不陌生,所以接下来首先讨论复制构造函数,它用于把一个对象复制到新创建的对象中,它接受一个对象的常量引用作为参数,声明格式如下:
ClassName(const ClassName & ) ;
什么情况下需要用到复制构造函数呢?就我所知有4种情况:
一、将新对象初始化为一个同类对象;
二、值传递对象给函数;
三、函数按值来返回对象;
四、编译器用来生成临时对象。
而在类中包含需要修改的静态变量或需要使用new初始化的成员指针是需要进行对复制构造函数的重新定义来进行深度复制。因为如果在构造函数中定义了对静态变量的更新而又不重新定义复制构造函数的话,那么隐式的复制构造函数将不会更新静态变量。析构函数却会在任何对象过期时调用,不论函数是被谁创建的。也就是说析构函数将同时更新通用构造函数和复制构造函数的对象。这就会引发问题,比如用一个静态数据成员来对对象进行计数时,将导致不同步,此时就需要进行深度复制。
还有就是在使用new时,因为隐式构造函数是对值进行复制的,此时复制的并不是一个实际的值,而是指针。就会导致对统一内存的析构函数的多次调用。此时就需要进行深度复制,来创建一个副本,并将副本地址赋予对象成员,而不是引用其他对象的地址。
赋值运算符是用来处理同类对象的赋值,默认赋值为成员赋值。需要重定义赋值运算符的原因与条件与重定义复制构造函数相同,实现类似但有一些差别。声明格式如下:
ClassName & operator=(const ClassName &);
还有一种实现是使用转换函数,不过这个以后讨论。
也因此传递对象是一般选择引用传递,而不是值传递,因为值传递会调用复制构造函数。
而移动构造函数则要先介绍C++中添加的右值引用,&&就是可以出现在赋值表达式右边,但不能对其使用地址运算符&的值,通过移动构造函数,可以避免移动数据,他还是在原来的地方,而只是修改了记录而已。移动构造函数何时触发? 那就是临时对象(右值)。用到临时对象的时候就会执行移动语义。
代码如下:
#include <iostream>
class A{
private:
char* ch;
public:
A(char* c = nullptr);
A(const A& str);
~A();
A& operator=(const A& str);
A& operator=(A&& str);
};
A::A(char* str)
{
if (!str)
{
ch= nullptr;
}
else
{
ch = new char[strlen(str) + 1];
strcpy(ch, str);
}
}
A::A(const A& str){
ch = new char[strlen(str.ch) + 1];
strcpy(ch, str.ch);
}
A& A::operator=(const A& str){
if (this == &str)
return *this;
delete[] ch;
ch = new char[strlen(str.ch) + 1];
strcpy(ch, str.ch);
return *this;
}
A& A::operator=(A&& str)
{
if (this == &str)
return *this;
delete[] ch;
ch = str.ch;
str.ch = nullptr;
return *this;
}
当然赋值运算符还有一种更高级的实现,考虑到异常安全性有
A& A::operator=(const A& str){
if (this == &str)
return *this;
A a(str);
char* temp = a.ch;
a.ch = ch;
ch = temp;
return *this;
}
在析构中来自动释放内存,跟为妥当。
并且C++11还增加了两个关键字default和delete,前者可以显示的声明这些方法的默认版本;后者可以禁止编译器使用特定的方法(而这在之前可以将其放在private部分)。如:
A() = default;
A(const A& str)=delete;
最后C++11还允许定义委托构造函数,这类似于在Java中的构造器中使用this();可以在一个构造函数定义中使用另一个构造函数,使用成员初始化列表:
A::A(char* str)
{
if (!str)
{
ch= nullptr;
}
else
{
ch = new char[strlen(str) + 1];
strcpy(ch, str);
}
}
A::A() :A("string"){/*do something*/ }<span style="white-space:pre"> </span>//委托构造函数