构造函数
对应的析构函数
1.概念
对于以下的日期类,可以通过SetDate公有的方法给对象设置内容。但每次创建对象都调用该方法过于麻烦。
构造函数在对象创建时,就完成了初始化。
class Date
{
public:
void SetDate(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Display()
{
cout <<_year<< "-" <<_month << "-"<< _day <<endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1,d2;
d1.SetDate(2021,5,1);
d1.Display();
Date d2;
d2.SetDate(2021,7,1);
d2.Display();
return 0; }
构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,保证每个数据成员都有一个合适的初始值,并且在对象的生命周期内只调用一次。
2.特性
构造函数是特殊的成员函数,需要注意的是,构造函数的虽然名称叫构造,但是需要注意的是构造函数的主要任务并不是开空间创建对象,而是初始化对象。
特性如下:
- 函数名与类名相同。
- 无返回值。
- 对象实例化时编译器自动调用对应的构造函数。
- 构造函数可以重载:
class Date
{
public :
// 1.无参构造函数
Date ()
{}
// 2.带参构造函数
Date (int year, int month , int day )
{
_year = year ;
_month = month ;
_day = day ;
}
private :
int _year ;
int _month ;
int _day ;
};
void TestDate()
{
Date d1; // 调用无参构造函数
Date d2 (2015, 1, 1); // 调用带参的构造函数
// 注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明
// 以下代码的函数:声明了d3函数,该函数无参,返回一个日期类型的对象
Date d3();
}
- 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成:
class Date
{
public:
/*
// 如果用户显式定义了构造函数,编译器将不再生成
Date (int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
*/
private:
int _year;
int _month;
int _day;
};
void Test()
{
// 没有定义构造函数,对象也可以创建成功,因此此处调用的是编译器生成的默认构造函数
Date d;
}
- 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认成员函数:
// 默认构造函数
class Date
{
public:
Date()
{
_year = 1900 ;
_month = 1 ;
_day = 1;
}
Date (int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
private :
int _year ;
int _month ;
int _day ;
};
// 以下测试函数能通过编译吗?
void Test()
{
Date d1; }
- 关于编译器生成的默认成员函数,很多童鞋会有疑惑:在我们不实现构造函数的情况下,编译器会生成默认的构造函数。但是看起来默认构造函数又没什么用?d对象调用了编译器生成的默认构造函数,但是d对象year/month/_day,依旧是随机值。
- 成员变量的命名风格:
// 我们看看这个函数,是不是很僵硬?
class Date
{
public:
Date(int year)
{
// 这里的year到底是成员变量,还是函数形参?
year = year;
}
private:
int year;
};
// 所以我们一般都建议这样
class Date
{
public:
Date(int year)
{
_year = year;
}
private:
int _year;
};
// 或者这样。
class Date
{
public:
Date(int year)
{
m_year = year;
}
private:
int m_year;
};
3.作用
构造函数在C++中扮演着非常重要的角色,其主要作用是在创建类的对象时初始化该对象的成员变量。以下是构造函数的详细作用描述:
- 初始化成员变量:
构造函数允许程序员为类的实例(对象)的成员变量设置初始值。这些初始值可以是固定的,也可以是基于传递给构造函数的参数来确定的。 - 确保对象在创建时处于有效状态:
通过正确地初始化成员变量,构造函数可以确保对象在创建时处于有效的、预期的状态。这有助于防止对象在创建时处于不确定或无效的状态,从而可能导致程序错误或不可预测的行为。 - 执行必要的设置和分配:
构造函数可以用于执行任何必要的设置或资源分配,这些设置或资源是对象在其生命周期内所需要的。例如,一个类可能包含指向动态分配内存的指针,构造函数可以负责分配这块内存,并初始化指针。 - 封装初始化逻辑:
通过将初始化逻辑封装在构造函数中,可以使得代码更加模块化和可维护。这意味着如果初始化逻辑需要改变,只需要修改构造函数的实现,而不需要修改类的其他部分或使用该类的代码。 - 支持不同的初始化方式:
通过重载构造函数(即定义多个具有不同参数列表的构造函数),可以为类的实例提供不同的初始化方式。这使得类更加灵活和易于使用,因为用户可以根据需要选择适当的初始化方式。 - 实现默认初始化:
如果没有为类定义任何构造函数,编译器会自动提供一个默认的构造函数(也被称为无参构造函数或默认构造函数)。然而,这个默认构造函数通常不会执行任何初始化操作,除非类的成员变量有默认初始化值。因此,定义自己的默认构造函数可以确保对象在创建时得到适当的初始化。 - 构造函数链:
在继承层次结构中,派生类的构造函数可以调用其基类的构造函数,以确保基类部分得到正确的初始化。这种机制被称为构造函数链或构造函数初始化列表。 - 防止对象被不当地初始化:
通过将构造函数设为私有或保护,并结合静态成员函数(如工厂方法),可以限制对象的创建方式,从而防止对象被不当地初始化。这在某些设计模式中(如单例模式)非常有用。 - 异常安全:
构造函数应该设计为异常安全的。这意味着在构造函数执行过程中发生异常时,应该能够确保对象的状态不会处于部分初始化或无效的状态。这通常通过确保构造函数中的所有操作都是原子的(即要么全部成功,要么全部失败)来实现。
总之,构造函数是C++中用于初始化对象状态的重要工具。通过正确地使用构造函数,可以确保对象在创建时处于有效的、预期的状态,并提供灵活、易于使用的初始化方式。
4.区别
在C++中,构造函数和析构函数是两种特殊的成员函数,它们在对象的生命周期中扮演着不同的角色。以下是构造函数和析构函数的主要区别:
构造函数 (Constructor)
- 定义:构造函数是特殊的成员函数,用于在创建对象时初始化对象的状态。
- 调用时机:当创建对象时(无论是通过直接声明、new操作符还是作为类的成员),构造函数会被自动调用。
- 名称:构造函数的名称与类名相同。
- 返回类型:构造函数没有返回类型(包括void)。
- 作用:构造函数用于初始化对象的成员变量,执行必要的资源分配,确保对象在创建时处于有效状态。
class MyClass {
public:
int x;
// 构造函数
MyClass(int value) : x(value) {
std::cout << "Object created with x = " << x << std::endl;
}
};
int main() {
MyClass obj(10); // 调用构造函数,输出 "Object created with x = 10"
return 0;
}
析构函数 (Destructor)
- 定义:析构函数也是特殊的成员函数,用于在对象生命周期结束时释放对象所占用的资源。
- 调用时机:当对象的生命周期结束时(无论是通过作用域结束、delete操作符还是作为类的成员对象被销毁),析构函数会被自动调用。
- 名称:析构函数的名称是在类名前加上波浪号(~)。
- 返回类型:析构函数没有返回类型(包括void)。
- 作用:析构函数用于执行清理操作,如释放动态分配的内存、关闭文件句柄等,确保对象被正确销毁。
class MyClass {
public:
int* ptr;
// 构造函数
MyClass(int value) {
ptr = new int(value);
std::cout << "Object created with ptr pointing to " << *ptr << std::endl;
}
// 析构函数
~MyClass() {
delete ptr;
std::cout << "Object destroyed, memory freed" << std::endl;
}
};
int main() {
MyClass obj(10); // 调用构造函数
// ... 对象的生命周期
// 当obj离开作用域时,析构函数被调用
return 0;
} // 输出 "Object destroyed, memory freed"
- 构造函数在创建对象时初始化对象的状态,确保对象在创建时处于有效状态。
- 析构函数在对象生命周期结束时释放对象所占用的资源,确保对象被正确销毁。
构造函数和析构函数是C++中面向对象编程的重要概念,它们一起确保了对象的正确创建和销毁,避免了资源泄漏和其他潜在问题。