如何通过示例使用 C++ 类构造函数和析构函数
构造函数和析构函数是 C++ 类概念的基础。构造函数和析构函数或多或少都类似于普通函数(但有一些差异),它们被提供来增强类的能力。
构造函数,顾名思义用于分配内存(如果需要)和构造类的对象,而析构函数用于在类对象被销毁时进行所需的清理。在本文中,我们将通过工作示例来研究构造函数和析构函数的概念。
构造函数和析构函数
让我们通过这个例子来理解构造函数和析构函数的概念:
#include<iostream>
class country
{
public:
country()
{
std::cout<<"\n Constructor called \n";
}
void setNumOfCities(int num);
int getNumOfCities(void);
~country()
{
std::cout<<"\n Destructor called \n";
}
private:
int num_of_cities;
};
void country::setNumOfCities(int num)
{
num_of_cities = num;
}
int country::getNumOfCities(void)
{
return num_of_cities;
}
int main(void)
{
country obj;
int num = 5;
obj.setNumOfCities(num);
num = obj.getNumOfCities();
std::cout<<"\n Number of cities is equal to "<<num;
return 0;
}
在上面的例子中:
- 类的名称是country。
- 观察到有两个函数与类的名称相同,即国家。
- 名称前有'~'的函数是析构函数,而另一个是构造函数。
正如我们已经讨论过的,构造函数用于创建对象。准确地说,构造函数是一个特殊的函数,在创建类的对象时会自动调用它。类似地,析构函数是一个特殊的函数,当类对象被删除或超出范围时,它会被自动调用。
让我们通过执行上面显示的代码来验证上面给出的解释。
以下是在 Linux 上编译 C++ 代码的方式:
$ g++ -Wall cnstrDestr.cpp -o cnstrDestr
$
所以我们看到我们使用 g++ 编译器来编译 c++ 代码,在我们的例子中,编译的代码没有任何警告或错误。现在让我们执行代码:
$ ./cnstrDestr
Constructor called
Number of cities is equal to 5
Destructor called
观察到当类的对象被创建时,类的构造函数被执行,并且就在对象即将被销毁时,析构函数被调用。所以这证实了这些特殊函数是在内部调用还是自动调用(从开发人员的角度来看)。
现在有人会问这些功能的实际作用是什么?在哪些场景中真正需要它们?
好吧,为了回答这个问题,让我们假设在同一个程序(如上所示)中,对 setNumOfCities() 和 getNumOfCities() 的调用是互换的。这意味着,现在代码尝试在设置之前获取该值。
这是更新的代码:
#include<iostream>
class country
{
public:
country()
{
std::cout<<"\n Constructor called \n";
}
void setNumOfCities(int num);
int getNumOfCities(void);
~country()
{
std::cout<<"\n Destructor called \n";
}
private:
int num_of_cities;
};
void country::setNumOfCities(int num)
{
num_of_cities = num;
}
int country::getNumOfCities(void)
{
return num_of_cities;
}
int main(void)
{
country obj;
int num = 5;
num = obj.getNumOfCities();
obj.setNumOfCities(num);
std::cout<<"\n Number of cities is equal to "<<num;
return 0;
}
执行此代码时,输出如下:
$ ./cnstrDestr
Constructor called
Number of cities is equal to 134514633
Destructor called
观察输出中产生了一些垃圾值。这是因为变量 'num_of_cities' 的值甚至在给它分配某个值之前就已获取。现在,这个问题的可能解决方案是什么?可以考虑使用类本身中的一些默认值来初始化变量。
就像是 :
#include<iostream>
class country
{
public:
country()
{
std::cout<<"\n Constructor called \n";
}
void setNumOfCities(int num);
int getNumOfCities(void);
~country()
{
std::cout<<"\n Destructor called \n";
}
private:
int num_of_cities = 0;
};
void country::setNumOfCities(int num)
{
num_of_cities = num;
}
int country::getNumOfCities(void)
{
return num_of_cities;
}
int main(void)
{
country obj;
int num = 5;
num = obj.getNumOfCities();
obj.setNumOfCities(num);
std::cout<<"\n Number of cities is equal to "<<num;
return 0;
}
嗯,我们可以这样做吗?让我们编译这段代码并验证:
$ g++ -Wall cnstrDestr.cpp -o cnstrDestr
cnstrDestr.cpp:23:25: error: ISO C++ forbids initialization of member ‘num_of_cities’ [-fpermissive]
cnstrDestr.cpp:23:25: error: making ‘num_of_cities’ static [-fpermissive]
cnstrDestr.cpp:23:25: error: ISO C++ forbids in-class initialization of non-const static member ‘num_of_cities’
好吧,编译器抛出错误,抱怨这不能完成,因为这个变量是非静态的。所以,这不是正确的做事方式。那么如何用默认值初始化变量呢?是的,你猜对了,通过构造函数。由于构造函数也是类的成员函数,因此它们可以访问类私有数据成员。
这是如何做到的:
#include<iostream>
class country
{
public:
country()
{
num_of_cities = 0;
std::cout<<"\n Constructor called \n";
}
void setNumOfCities(int num);
int getNumOfCities(void);
~country()
{
std::cout<<"\n Destructor called \n";
}
private:
int num_of_cities;
};
void country::setNumOfCities(int num)
{
num_of_cities = num;
}
int country::getNumOfCities(void)
{
return num_of_cities;
}
int main(void)
{
country obj;
int num = 5;
num = obj.getNumOfCities();
obj.setNumOfCities(num);
std::cout<<"\n Number of cities is equal to "<<num;
return 0;
}
现在让我们编译并执行上面的代码:
$ g++ -Wall cnstrDestr.cpp -o cnstrDestr
$ ./cnstrDestr
Constructor called
Number of cities is equal to 0
Destructor called
观察编译成功并产生了预期的输出。所以,这应该让你对构造函数和析构函数的力量有一个很好的了解。
在实际场景中,构造函数用于初始化类的数据成员,最重要的是为指针分配内存,析构函数用于清理此内存。
这是一个例子:
#include<iostream>
class country
{
public:
country()
{
num_of_cities = new(int);
std::cout<<"\n Constructor called \n";
}
void setNumOfCities(int num);
int getNumOfCities(void);
~country()
{ if(num_of_cities) delete num_of_cities;
std::cout<<"\n Destructor called \n";
}
private:
int *num_of_cities;
};
void country::setNumOfCities(int num)
{
*num_of_cities = num;
}
int country::getNumOfCities(void)
{
return (*num_of_cities);
}
int main(void)
{
country obj;
int num = 5;
obj.setNumOfCities(num);
num = obj.getNumOfCities();
std::cout<<"\n Number of cities is equal to "<<num;
return 0;
}
以下是有关构造函数和析构函数的一些要点:
- 每当创建和销毁类对象(或超出范围)时都会调用它们。
- 构造函数和析构函数通常在范围内保持公开。
- 构造函数和析构函数都与类同名,并且没有返回类型。这意味着它们不能像任何其他普通函数一样返回值。
- 如果没有明确提供构造函数或析构函数,编译器会在内部生成一个。
- 默认构造函数,如果在类中显式声明,则是不接受参数或具有默认值参数的构造函数。
- 构造函数和析构函数不能被继承。
- 构造函数可以重载。
- 析构函数不能接受参数。
- 点赞
- 收藏
- 关注作者
评论(0)