构造函数
是一种特殊的成员函数,主要功能是为对象分配存储空间,以及为类成员变量赋初值
可以重载
构造函数名必须与类名相同
没有任何返回值和返回类型
创建对象自动调用,不需要用户来调用,且只调用一次
类没有定义任何构造函数,编译系统会自动为这个类生成一个默认的无参构造函数
构造函数定义
1.类中定义
2.类中声明类外定义
构造函数的调用
1.显式调用
显式调用有参构造:Data ob=Data(20);
显式调用无参构造:Data ob=Date();
2.隐式调用
隐式调用有参构造:Data ob(20);
隐式调用无参构造:Data ob;
3.隐式转换的方式调用有参构造(针对只有一个数据成员)【尽量别用】
Data ob=30;-----Data ob(30);
4.匿名对象
Data(4);//当前语句结束,匿名对象立即释放
5.指针调用
类名 *指针变量名=new 类名(实参列表)---Score *stu=new Score(99,100);
带默认参数的构造函数
带初始化列表的构造函数
成员初始化列表:类名::构造函数名(参数) : (成员初始化列表) { }
初始化成员变量,除了使用构造函数初始化外,还可以用成员初始化列表
特别:当类的对象作为另一个类的成员时,可以显示调用对象成员的构造函数
顺序:对象成员的构造函数---自己的构造函数---析构自己---析构对象成员
注意:类成员是按照它们在类里被声明的顺序进行初始化的,与它们在成员初始化列表中列出的顺序无关。
构造函数重载
构造函数名字相同,参数个数和参数类型不一样。
析构函数
是一种特殊的成员函数,当对象的生命周期结束时,用来释放分配给对象的内存空间,并做一些清理的工作。
不能重载
析构函数名与类名必须相同,并且析构函数名前面必须加一个波浪号~。
没有参数,没有返回值,。
一个类中只能有一个析构函数。
没有定义析构函数,编译系统会自动为和这个类生成一个默认的析构函数。
析构函数的定义
1.类中定义
2.类中声明类外定义
必须使用初始化列表的四种情况
1.类成员为const类型时
常量成员,因为常量只能初始化不能赋值,所以必须放在初始化列表里面。
2.类成员为引用类型时
引用类型,必须在定义的时候初始化,并且不能重新赋值,所以也要写在初始化列表里面
3.如果类存在继承关系,派生类必须在其初始化列表中调用基类的构造函数
拷贝构造函数
是一种特殊的构造函数。其形参是本类对象的常引用。
函数名和类名相同,并且也没有返回值
同类对象都有一个拷贝构造函数
拷贝函数只有一个参数---同类对象的引用,为什么是引用类型?
如果拷贝构造函数使用值传递会发生什么?
如果使用的值传递,那么在形参与实参相结合时,又要调用本类的拷贝构造函数,导致进入死循环
如果使用引用传递(const 引用)来调用拷贝构造函数,则只会传递对象的引用,而不会创建新的副本
拷贝构造函数的定义
1.类中定义
2.类中声明类外定义
调用拷贝构造函数的三种情况
1.旧对象初始化新对象,会调用拷贝构造函数
class Baz { public: int value; // 拷贝构造函数 Baz(const Baz &other) : value(other.value) {} // 构造函数 Baz(int val = 0) : value(val) {} }; int main() { Baz baz1(42); Baz baz2 = baz1; // 触发拷贝构造函数的调用 return 0; }
2.当函数的形参是类的对象,调用函数进行形参和实参结合时,会调用拷贝构造函数
class Foo { public: int value; // 拷贝构造函数 Foo(const Foo &other) : value(other.value) {} // 构造函数 Foo(int val = 0) : value(val) {} }; void someFunction(Foo foo) { // ... } int main() { Foo foo(42); someFunction(foo); // 触发拷贝构造函数的调用 return 0; }
3.当函数的返回值是对象,函数执行完成返回调用者时,会调用拷贝构造函数
class Bar { public: int value; // 拷贝构造函数 Bar(const Bar &other) : value(other.value) {} // 构造函数 Bar(int val = 0) : value(val) {} }; Bar someFunction() { Bar bar(42); return bar; // 触发拷贝构造函数的调用 } int main() { Bar bar2 = someFunction(); return 0; }
区别:
自定义拷贝构造函数
原因:深拷贝和浅拷贝
浅拷贝:由默认的拷贝构造函数所实现的数据成员逐一赋值。大多数情况下默认构造函数是可以胜任这项工作的,但如果类中存在指针类型的数据,浅拷贝就会发生错误。
上述错误是因为stu1和stu2所指的内存空间相同,在析构函数释放stu1所指的内存后,再释放stu2所指的内存会发生错误,因为此内存空间已被释放。解决方法就是重定义拷贝构造函数,为其变量重新生成内存空间。
默认构造函数的调用规则
系统会给任何一个类提供3个成员函数:默认构造函数,默认析构函数,默认拷贝函数
1.如果用户提供了有参构造,将屏蔽系统的默认构造函数
//可行 class A { private: int a; public: }; int main() { A x; return 0; } //报错 class A { private: int a; public: A(int t):a(t){} }; int main() { A x; return 0; }
2.如果用户提供了有参构造,不会屏蔽系统的默认拷贝构造函数
class A { private: int a; public: A(int t):a(t){} int Geta(){ return a; } }; int main() { A x1(10); A x2=x1; cout<<x2.Geta();//输出10 return 0; }
3.如果用户提供了拷贝构造函数,将屏蔽系统的默认构造函数、默认拷贝构造函数
class A { private: int a; public: A(const A &b){ a=b.a; };//拷贝构造函数中可以访问到private和protected int Geta(){ return a; } }; int main() { A x;//报错 return 0; }
注意:普通类外函数在引用一个类的实例时,无法访问其private和protected。但是该类的拷贝构造函数可以访问到其private和protected
总结:对于一个类,用户一般要实现:无参构造(初始化)、有参构造(赋值)、拷贝构造(深拷贝)、析构(释放空间)。