默认构造函数
默认构造函数无需任何实参。当类没有声明任何构造函数的时候,编译器才会自动声明一个。对于普通类,必须定义它的默认构造函数。 =default 完成默认构造函数。 构造函数建议使用类内初始值。
class A
{
public:
A():i(0){}
private:
int i;
};
默认初始化出现的情况:
1.当块作用域内不使用任何初始值定义一个非静态变量或者数组
2.当类本身含有的类类型的成员且使用合成的默认构造函数
3.当类类型的成员没有在构造函数初始值列表中显式的初始化时
值初始化的情况:
1.当数组初始化中如果提供的初始值数量小于数组的大小时
2.不使用初始值定义一个局部静态变量时
3.通过书写形如T()的表达式显式的请求值初始化时
class A
{
public:
int i;
};
int main()
{
A sss = A();
cout << sss.i << endl; //输出0
}
特殊情况是,类中缺少默认构造函数的情况
class Nf
{
public:
Nf(Nf&){} // 由于定义了构造函数,编译器不会生成默认构造函数,从而没有默认构造函数
};
struct ANF
{
Nf nn;
};
int main()
{
ANF sss;//无法调用其成员nn的默认构造函数,程序无法通过编译。解决方案就是在Nf类中增加默认构造函数即可
}
默认初始化
如果内置类型的变量未被显示初始化,它的值由定义的位置决定。定义于任何函数体之外的变量被初始化为0。定义于函数体内部的内置类型变量将不被初始化。
类的静态成员
静态数据成员定义在任何函数之外, static的作用域是声明它的整个文件都是可见的,文件之外不可见
class MMM
{
public:
MMM() = default;
static int s;
};
int MMM::s = 10;
int main()
{
}
拷贝构造函数
如果没有为一个类定义拷贝构造函数,编译器会为我们定义一个合成拷贝构造函数。合成的拷贝构造函数会将参数的成员逐个拷贝到正在创建的对象中,编译器从给定的对象中依次将非static成员拷贝到正在创建的对象中。
–
类成员
调用其拷贝构造函数
–
内置类型
直接拷贝,虽然数组不能直接拷贝,但是会逐元素的拷贝一个数组类型的成员。如果数组类型是类成员,则使用元素的拷贝构造函数来进行拷贝
拷贝初始化的发生情况:
= 定义变量时
将对象实参传递给非引用型形参
从一个返回类型为非引用型的函数返回一个对象
用花括号列表初始化一个数组中的元素或者一个聚合类的成员。
emplace_back的小坑
emplace_back 需要实现移动构造函数~~~
原因可能是,编译器优化导致其绕过移动构造函数,但是移动构造函数必须是自定义实现的,否则不会进行类似的优化。
class TT
{
public:
int i;
TT(int i)
{
std::cout << "I am being constructed.\n";
}
TT(TT&) =delete;
TT(TT&& other){} //一定要实现
TT& operator=(const TT& other) =delete;
};
int main()
{
std::vector<TT> s2;
s2.emplace_back(1994);
}
销毁类类型需要执行自己的析构函数。内置类型不需要析构函数。隐式的销毁内置指针类型的成员不会delete它所指向的对象。当指向一个对象的引用或者指针离开作用域时,析构函数不会被执行。
析构函数执行时间:
1.变量离开作用域
2.对象被销毁时,其成员被销毁
3.容器(无论是标准库容器还是数组)被销毁时,其元素被销毁
4.对于动态分配的对象,当对指向它的指针应用delete运算符时被销毁
5.对于临时对象,当创建它的完整表达式结束时被销毁
析构函数有一个函数体和一个析构部分。在析构函数里, 首先执行函数体,再销毁成员,成员按照初始化顺序的逆序被销毁。
class M1
{
public:
~M1()
{
std::cout << "M1 is execute" << std::endl;
};
};
class M2
{
public:
~M2()
{
std::cout << "M2 is execute" << std::endl;
};
};
class M3
{
public:
M1 m1_;
M2 m2_;
M3(M1& m1, M2 &m2)
{
m1_ = m1;
m2_ = m2;
}
~M3()
{
std::cout << "M3 is execute" << std::endl;
};
};
int main()
{
M1 m1;
M2 m2;
M3 m3(m1,m2); //先执行M3 的析构函数,在按照初始化顺序的逆序依次调用M2,M1的析构函数, 析构部分释放掉M3,M2,M1。 最后把m2,m1释放掉
}
一般情况下, 如果类成员不需要写析构函数体,编译器默认合成析构函数体。如果需要写,则说明有动态内存,需要自己手动释放。由于合成拷贝构造和合成拷贝赋值函数只是简单复制,不会复制指针所指向的内存。所以需要在析构函数体中手动释放内存的,一定需要在拷贝构造和拷贝赋值函数的函数体里手动复制操作。同样的,反过来也是一样,如果需要拷贝构造或者拷贝赋值函数的,一定需要手动析构,否则内存泄露。
如果类有数据成员不能默认构造、拷贝、复制、析构,则对应的成员函数被定义为删除的。如果成员的析构函数如果被定义删除的,则该成员不能被创建或者释放指向该对象的指针,从而导致默认构造函数和拷贝构造函数被定义为删除的。
对于有引用成员或const 成员的类,由于不能将一个新值赋予一个const对象, 不能将一个新值赋予一个引用(这样做改变的引用所指向的对象的值),所以对于有这些成员的类的默认构造函数和合成拷贝赋值运算符被定义为删除的
对成员函数可以声明却不定义的,是阻止其被访问的很好的方案
void test();
int main()
{
test(); //无法通过编译
}
定义行为像值和像指针的类,异常安全赋值和拷贝并交换的技术
class HasPtrV
{
public:
friend void swap(HasPtrV &lrhs, HasPtrV &rhs);
HasPtrV(const std::string &s = std::string()) :ps(new std::string(s)), i(0)
{
cout << *ps << "construct" << endl;
}
HasPtrV(HasPtrV& p)
{
ps = new std::string(*p.ps);
i = p.i;
cout << *ps << "copy construct" << endl;
}
HasPtrV & operator=(HasPtrV p)//拷贝并交换的技术(同HasPtr)
{
swap(*this, p);
return *this;
}
/*HasPtrV & operator=(HasPtrV &p)
{
if (ps == p.ps)
{
cout << *ps << "self assign" << endl;
return *this;
}
cout << *ps << "assign" << endl;
auto t = new std::string(*p.ps);
delete ps;
ps = t;
i = p.i;
return *this;
}*/
~HasPtrV()
{
cout << *ps << " delete" << endl;
delete ps;
ps = nullptr;
}
void print(std::string head = "")
{
cout << head << " : now use is " << *ps << endl;
}
private:
std::string *ps;
int i;
};
inline void swap(HasPtrV &lrhs, HasPtrV &rhs)
{
using std::swap;
swap(lrhs.ps, rhs.ps);
swap(lrhs.i, rhs.i);
}
class HasPtr
{
public:
friend void swap(HasPtr &lhs, HasPtr &rhs);
HasPtr(const std::string &s = std::string()):ps(new std::string(s)), i(0), use(new std::size_t(1))
{
cout << "construct" << endl;
}
HasPtr(HasPtr &p):ps(p.ps),i(p.i),use(p.use)
{
cout << "copy construct" << endl;
++*use;
}
HasPtr & operator=(HasPtr p) //拷贝并交换。赋值时将p作为参数传过来,拷贝了p的副本,用这个副本完成交换操作
{
swap(*this, p);
return *this;
}
/*HasPtr & operator=(HasPtr& p)//异常安全赋值
{
++*p.use;//处理自赋值
if (--*use == 0)
{
cout << "delete old" << endl;
delete ps;
delete use;
ps = nullptr;
use = nullptr;
}
ps = p.ps;
i = p.i;
use = p.use;
cout << "assign" << endl;
return *this;
}
*/
~HasPtr()
{
cout << "delete" << endl;
if (--*use == 0)
{
cout << "delete OLD" << endl;
delete ps;
delete use;
ps = nullptr;
use = nullptr;
}
}
void print(std::string head="")
{
cout << head << " : now use is " << *use << " " << *ps << endl;
}
private:
std::size_t *use;
std::string *ps;
int i;
};
inline void swap(HasPtr &lhs, HasPtr &rhs)
{
std::cout << "swap" << endl;
swap(lhs.ps, rhs.ps);
std::swap(lhs.i, rhs.i);
}
template<typename T>
void test()
{
T s1("haha");
s1.print("s1");
s1 = s1;
T s2("test");
s2.print("s2");
T s3(s2);
s2.print("s2");
s3.print("s3");
s3 = s1;
s1.print("s1");
s2.print("s2");
s3.print("s3");
}
class Foo
{
public:
friend void swap(Foo &lhs, Foo &rhs);
Foo(HasPtr &s = HasPtr()):data(s){}
private:
HasPtr data;
};
void swap(Foo &lhs, Foo &rhs)
{
swap(lhs.data, rhs.data);
}
int main()
{
test<HasPtr>();
test<HasPtrV>();
HasPtr s1("haha");
HasPtr s2("heihei");
Foo f1(s1);
Foo f2(s2);
swap(f1, f2);
}