类的构造函数
类的 构造函数是类的一种特殊的成员函数,它会在每次创建类的新对象时执行。
类名::类名(形参总表) : 类名(形参总表) : 成员1(参数1), 成员2(参数2) { }
|
创建对象方式:
类名 对象名; 类名 对象名(参数);
类名* 对象指针 = new 类名; 类名* 对象指针 = new 类名(参数);
|
构造函数特殊性:
#include <iostream> using namespace std; class Line { public: void setLength( double len ); double getLength( void ); Line(double len); private: double length; };
Line::Line( double len) { cout << "Object is being created, length = " << len << endl; length = len; } void Line::setLength( double len ) { length = len; } double Line::getLength( void ) { return length; }
int main( ) { Line line(10.0); cout << "Length of line : " << line.getLength() <<endl; line.setLength(6.0); cout << "Length of line : " << line.getLength() <<endl; return 0; }
|
使用初始化列表来初始化字段
使用初始化列表来初始化字段:
Line::Line( double len): length(len) { cout << "Object is being created, length = " << len << endl; }
|
上面的语法等同于如下语法:
Line::Line( double len) { length = len; cout << "Object is being created, length = " << len << endl; }
|
初始化列表与类中赋值方式的不同参考:C++初始化列表与赋值
假设有一个类 C,具有多个字段 X、Y、Z 等需要进行初始化,同理地,您可以使用上面的语法,只需要在不同的字段使用逗号进行分隔,如下所示:
C::C( double a, double b, double c): X(a), Y(b), Z(c) { .... }
|
类的析构函数
类的 析构函数是类的一种特殊的成员函数,它是在对象的生存期即将结束的时刻被自动调用的。
析构函数的名称与类的名称是完全相同的,只是在前面加了个波浪号(~)作为前缀,它不会返回任何值,也不能带有任何参数。析构函数通常用于释放在构造函数或在对象生命期内获取的资源。
#include <iostream> using namespace std; class Line { public: void setLength( double len ); double getLength( void ); Line(); ~Line(); private: double length; };
Line::Line(void) { cout << "Object is being created" << endl; } Line::~Line(void) { cout << "Object is being deleted" << endl; } void Line::setLength( double len ) { length = len; } double Line::getLength( void ) { return length; }
int main( ) { Line line; line.setLength(6.0); cout << "Length of line : " << line.getLength() <<endl; return 0; }
|
调用顺序
1、构造函数的调用顺序
基类构造函数、对象成员构造函数、派生类本身的构造函数
2、析构函数的调用顺序
派生类本身的析构函数、对象成员析构函数、基类析构函数(与构造顺序正好 相反)
#include <iostream> using namespace std; class A { public: A(){cout<<"A::constructor"<<endl;}; ~A(){cout<<"A::deconstructor"<<endl;}; }; class B { public: B(){cout<<"B::constructor"<<endl;}; ~B(){cout<<"B::deconstructor"<<endl;}; }; class C : public A { public: C(){cout<<"C::constructor"<<endl;}; ~C(){cout<<"C::deconstructor"<<endl;}; private:
B b; }; class D : public C { public: D(){cout<<"D::constructor"<<endl;}; ~D(){cout<<"D::deconstructor"<<endl;}; };
int main(void) { C* pd = new D(); delete pd; return 0; }
|
static B b
:B
类的构造函数没有被调用的原因是因为 B
类对象 b
被声明为静态成员变量。静态成员变量在类的定义中只是声明,需要在类外部进行定义和初始化。
拷贝构造函数&拷贝赋值运算符
拷贝构造函数和拷贝赋值运算符用于在对象之间进行数据的拷贝操作。它们的定义和功能有所不同。
拷贝构造函数用于创建一个新对象,该对象与另一个已存在的同类对象具有相同的值和属性。它的定义形式为: 类名(const 类名& 对象名)
,const不是必须的,形参为本类的对象引用。
拷贝构造函数通常用于以下情况:
-
通过赋值或参数传递方式将一个对象的值复制给另一个对象。
-
当函数返回一个对象时,会调用拷贝构造函数来生成一个临时副本。
-
当对象作为参数传递给函数时,会调用拷贝构造函数来生成参数对象的副本。
class MyClass { public: MyClass(const MyClass& obj) { this->data = obj.data; } private: int data; };
int main() { MyClass obj1; MyClass obj2(obj1); return 0; }
|
拷贝赋值运算符用于将一个已存在的对象的值赋给另一个已存在的对象。它的定义形式为:类名& operator=(const 类名& 对象名)
。拷贝赋值运算符通常用于以下情况:
class MyClass { public: MyClass& operator=(const MyClass& obj) { this->data = obj.data; return *this; } private: int data; };
int main() { MyClass obj1; MyClass obj2; obj2 = obj1; return 0; }
|
总结:
-
拷贝构造函数用于创建一个新对象,并将另一个对象的值复制给该新对象。
-
拷贝赋值运算符用于将一个已存在的对象的值赋给另一个已存在的对象。
-
拷贝构造函数在对象创建、返回和参数传递时调用,拷贝赋值运算符在对象赋值时调用。
移动构造函数&移动赋值运算符
在C++中,移动构造函数和移动赋值运算符的引入解决了对象复制时性能低下的问题,特别是当对象包含动态分配的资源时。移动操作允许资源的所有权从一个对象转移到另一个对象,而不必进行资源的深拷贝,这些资源通常是临时资源。
当满足以下条件时,C++会使用移动操作:
-
目标对象是右值(例如临时对象或返回的函数值)。
-
类提供了移动构造函数或移动赋值运算符。
#include <iostream> #include <utility>
class MyClass { private: int* data; public: MyClass(int value) : data(new int(value)) { std::cout << "构造函数被调用。值: " << *data << std::endl; }
~MyClass() { delete data; std::cout << "析构函数被调用。" << std::endl; }
MyClass(const MyClass& other) : data(new int(*other.data)) { std::cout << "拷贝构造函数被调用。" << std::endl; }
MyClass(MyClass&& other) noexcept : data(other.data) { other.data = nullptr; std::cout << "移动构造函数被调用。" << std::endl; }
MyClass& operator=(const MyClass& other) { if (this == &other) return *this; delete data; data = new int(*other.data); std::cout << "拷贝赋值运算符被调用。" << std::endl; return *this; }
MyClass& operator=(MyClass&& other) noexcept { if (this == &other) return *this; delete data; data = other.data; other.data = nullptr; std::cout << "移动赋值运算符被调用。" << std::endl; return *this; }
void show() const { if (data) std::cout << "值: " << *data << std::endl; else std::cout << "值: nullptr" << std::endl; } };
int main() { MyClass obj1(10); MyClass obj2 = std::move(obj1);
obj2.show(); obj1.show();
MyClass obj3(20); obj3 = std::move(obj2);
obj3.show(); obj2.show();
return 0; }
|
代码解析:
移动构造函数 (MyClass(MyClass&& other)
): 将资源指针从other
对象转移到当前对象,并将other
的指针设为nullptr
。
移动赋值运算符 (MyClass& operator=(MyClass&& other)
): 在进行自赋值检查后,释放当前对象的资源,然后将other
的资源转移到当前对象,最后将other
的指针设为nullptr
。
explicit显示声明
explicit
关键字用于修饰构造函数,防止隐式类型转换。默认情况下,构造函数是隐式的,允许通过类型转换自动生成对象。但这种隐式转换可能导致意外行为。
使用 explicit
后,构造函数必须显式调用,避免不必要的隐式转换。这通常适用于只有一个参数的构造函数。如果构造函数的其他参数都有默认值,即使有多个参数,explicit
仍然有效。
#include <iostream>
class MyClass { public: explicit MyClass(int x, int y = 0) : value(x + y) {} void display() { std::cout << "Value: " << value << std::endl; }
private: int value; };
int main() { MyClass obj(42); obj.display(); return 0; }
|
浅拷贝与深拷贝
浅拷贝指的是对象的复制过程仅仅拷贝对象的字段值,而不重新分配动态内存。对于成员变量是指针的类,浅拷贝会导致多个对象指向同一块内存区域。
在浅拷贝情况下,如果一个对象释放了内存,另一个对象的指针将成为野指针,导致程序崩溃或出现不可预测的行为。典型的问题是析构函数释放同一块内存多次。
深拷贝通过复制过程重新分配动态内存,使得每个对象拥有自己的内存副本,从而避免多个对象共享同一块内存。深拷贝通常通过自定义拷贝构造函数或赋值运算符来实现。
#include <iostream> #include <cstring>
class MyClass { public: MyClass(const char* str) { data = new char[strlen(str) + 1]; strcpy(data, str); }
MyClass(const MyClass& other) { data = new char[strlen(other.data) + 1]; strcpy(data, other.data); }
~MyClass() { delete[] data; }
void display() { std::cout << "Data: " << data << std::endl; }
private: char* data; };
int main() { MyClass obj1("Hello"); MyClass obj2 = obj1;
obj1.display(); obj2.display();
return 0; }
|
Reference
[1] C++ 类构造函数 & 析构函数: https://www.runoob.com/cplusplus/cpp-constructor-destructor.html