一、简介
在C++中,构造函数是一种特殊的成员函数,用于初始化类的对象。构造函数的名称与类的名称相同,没有返回值类型,也不需要使用void关键字。构造函数可以有参数和重载
,因此可以根据不同的参数列表来创建不同的对象。
构造函数在对象创建时自动调用,用于完成对象的初始化工作,例如为成员变量赋初值、分配内存等。可以在构造函数中使用成员初始化列表来为成员变量赋初值,也可以在构造函数体中进行赋值操作。
二、构造函数
C ++利用构造函数实现了对象所占内存赋值。对象初始化时强制执行构造函数,如果我们没有自己实现构造函数,编译器将会提供默认的构造函数
构造函数是一种特殊的成员函数,它主要用于为对象分配空间,进行初始化。构造函数的名字必须与类名相同,不能由用户任意命名。它可以有任意类型的参数,但不能具有返回值。它不需要用户来调用,而是在建立对象时由编译器自动执行。我们使用 new 时,会先调用 malloc 申请一块堆区空间,然后调用构造函数为申请的空间赋值。
2.1 构造函数分类
构造函数有以下几种特殊的类型:
- 默认构造函数: 没有参数的构造函数,如果没有定义任何构造函数,则编译器会自动生成一个默认构造函数。
- 拷贝构造函数: 用于将一个已存在的对象复制到新创建的对象中,参数为同类型的引用或常量引用。
- 移动构造函数: 用于将一个右值引用的对象的资源转移给新创建的对象,参数为同类型的右值引用。
- 析构函数: 用于释放对象所占用的资源,没有参数和返回值类型,名称为类名前加上“~”。
构造函数的定义和使用示例:
class MyClass{
public:
//默认构造函数
MyClass(){
num = 0;
str = "default";
}
//有参数的构造函数
MyClass(int n, string s){
num = n;
str = s;
}
//拷贝构造函数
MyClass(const MyClass& other){
num = other.num;
str = other.str;
}
//移动构造函数
MyClass(MyClass&& other){
num = other.num;
str = std::move(other.str);
//转移右值引用的资源
other.num = 0;
other.str.clear();
}
//析构函数
~MyClass(){
//释放资源
}
private:
int num;
string str;
};
int main() {
MyClass obj1; // 调用默认构造函数
MyClass obj2(10, "Hello"); // 调用有参数的构造函数
MyClass obj3 = obj1; // 调用拷贝构造函数
MyClass obj4 = std::move(obj2); // 调用移动构造函数
return 0;
}
2.2 构造函数语法
- 构造函数的名字必须与类名相同,否则编译程序将把它当做一般的成员函数来处理;
- 构造函数没有返回值,在定义构造函数时,是不能说明它的类型的;
- 与普通的成员函数一样,构造函数的函数体可以写在类体内,也可写在类体外;
- 构造函数一般声明为公有成员,但它不需要也不能像其他成员函数那样被显式地调用,它是在定义对象的同时被自动调用,而且只执行一次;
- 构造函数可以不带参数。
#include<iostream>
using namespace std;
#include<string>
#include<vector>
/*
private 私有的:只有当前类内才有权限访问这个成员(类内)
protected 受保护的:类内和子类有权限访问这个成员(类内子类)
public 公共的:所有位置都有权限访问这个成员(对象,子类,类内)
*/
class people {
private: //私有的 不可以被对象访问
int age;
protected: //受保护的 内类和子类能访问
bool Gender;
public: //共有的类内 子类 对象都可以访问
string name;
//构造函数的名字和类名相同,有参数(能重载),没有返回值
people() {
cout << "无参构造" << endl;
}
people(int A) {
cout << "调用了参数为int A的构造函数" << endl;
}
/*people(int A, bool G, string N) {
age = A;
Gender = 0;
name = N;
cout << "调用了参数为int A,bool G,string N的构造函数" << endl;
}*/
people(int age, bool Genger, string name) {
this->age = age;//this 是个指针,指向了当前对象,this的主要作用是区分成员变量和参数
}
};
int main() {
people p1(12);
people p2(12, 1, "asd");
people p3;//调用无参构造 如果是people p() 编译器会认为是函数声明不会创建对象
cout << -------------------------- << endl;
//在堆区创建对象C++中使用关键字 new,释放堆区内存使用关键字delete
people *Q = new people();//调用了无参构造函数在堆区创建了对象
people* Q1 = new people(1);//调用了参数为int A 的构造函数创建了堆区对象
//释放堆区内存
delete Q;
delete Q1;
//在堆区创建int类型的数组
int* int_ps = new int[3];//在堆区申请了12个字符的内存
cout << int_ps[0] << int_ps[1] << int_ps[2] << endl;
delete[]int_ps;//释放堆区数组要加[]
//在堆区创建people类型的数组
people* peo_ps = new people[3];//在堆区创建了3个people类型的对象
delete[]peo_ps;
return 0;
}
在建立对象的同时,采用构造函数给数据成员赋值,通常有以下量中形式:
类名 对象名[(实参表)]
Score op1(99, 100);
op1.showScore();
类名 *指针变量名 = new 类名[(实参表)]
Score *p;
p = new Score(99, 100);
p->showScore();
-----------------------
Score *p = new Score(99, 100);
p->showScore();
三、析构函数
在C++中,析构函数是类中的一种特殊成员函数,用于在对象被销毁时进行资源的释放和清理操作 (清空对象内部指针指向的堆区内存)。析构函数名称与类名相同,但在名称前边加上一个波浪号(~)。
**析构函数没有返回值,也没有参数,页不能重载。
**因此
其声明形式如下:
class MyClass{
public:
//构造函数
MyClass();
//析构函数
~MyClass();
};
当对象被销毁时,其析构函数会自动被调用。 对象的销毁可以是由程序员显式地调用 delete 操作符、delete[] 操作符或者在对象的声明周期结束时自动地调用析构函数(栈区对象自动释放,堆区对象需要手动调用 delete 或 delete[] 操作符调用)。
因为析构函数的主要作用是释放资源和清理操作,所以我们通常在析构函数中完成一些对象的清理操作,例如释放动态分配的内存、关闭文件句柄、释放网络连接等操作。
下面是一个简单的示例,演示了如何在析构函数中释放动态分配的内存:
#include<iostream>
#include<string> //C++中,string是类,里面包含了方法和属性
using namespace std;
class MyClass{
int a;
int b;
int* p = NULL;
int* p1 = NULL;
public:
MyClass(){}
MyClass(int a){
p = new int(a);
p1 = new int [a];
}
~MyClass(){
if(p) delete p;
if(p1) delete[] p1;
cout << "析构函数" << endl;
}
};
int main(){
MyClass p(5);
MyClass* p1 = new MyClass(6);
delete p1;
return 0;
}
说明: 在以下情况下,当对象声明周期结束时,析构函数会被自动调用:
- 对象在栈区创建时,当对象超出作用域范围时,它的析构函数就会自动被调用
void someFunction(){
MyClass obj; //创建对象
//对象声明周期结束,离开作用域
} //obj的析构函数自动被调用
- 对象在堆区创建时,当使用 delete 操作符释放对象时,对象的析构函数就会被自动调用
void someFuntion(){
MyClass* pObj = new MyClass(0; //在堆区创建对象
//在这里使用对象
delete pObj; //释放对象
//pObj的析构函数自动被调用
}
- 对象作为另一个对象的成员时,当包含该对象的对象被销毁时,该对象的析构函数就会被自动调用
class MyContainer{
public;
MyContainer(){
pObj = new MyClass(); //创建一个MyClass对象
}
~Mycontainer(){
delete pObj; //释放MyClass对象
}
private:
MyClass* pObj;
};
在上面的示例中,当MyContainer对象被销毁时,他所包含的M有Class对象的析构函数也会被自动调用。
代码示例:
#include<iostream>
#include<string>//C++中 string是类,里面包含了方法 和 属性
#include<vector>
using namespace std;
class A {
public:
//析构函数
int* p; //p属于对象,对象在堆区,p就在堆区
A(int size) {
p = new int[size];
}//堆区没有释放,会造成内存泄漏
//栈区对象自动释放,堆区对象手动释放,都会自动调用析构函数
//析构函数
~A() {
if (p) {
delete p;
p = nullptr; //将p赋值成空指针
}
cout << "析构函数" << endl;
}
};
int main(){
//A a(3); //错误
A* a = new A(3); //p指针又指向一个堆区
//不执行析构函数
//析构函数是在释放对象时自动调用
delete a;//释放a指向的堆区对象,从而调用析构函数
return 0;
}//析构函数是为了释放对象里面的指针 指向堆区内存
----------------------------------------------------------------------------------
//输出“析构函数”
四、默认的构造函数和析构函数
默认构造函数和析构函数是C++中每个类都具有的饮食成员函数,如果没有显示定义,则会按照编译器提供的默认实现执行。
4.1 作用
- 默认构造函数(没有参数的构造函数): 在创建类对象时,用于初始化对象的成员变量。如果没有显式定义构造函数,编译器会提供一个默认的构造函数,该函数不带任何参数,并将对象的成员变量初始化为默认值(比如数值类型初始化为0、指针类型初始化为nullptr、布尔类型初始化为false等)。
- 默认析构函数(没有参数的析构函数): 在对象生命周期结束时,用于释放对象所占有的资源。
如果对象有成员变量是指针类型,那么默认析构函数只会释放这些指针所指向内存,而不会释放指针本身的内存。
如果没有显式定义析构函数,编译器会提供一个默认的析构函数,该函数不带任何参数,并释放对象所占用的内存空间。
注意:
如果类中有成员变量是指针类型,并且这些指针指向的内存是通过 new 运算符分配的,那么就需要自己编写析构函数来释放这些内存。否则类中可能会出现内存泄露问题。
例如,下面是一个简单的类示例,该类包含了一个整型成员变量和一个指针类型的成员变量:
class MyClass{
public:
MyClass(); //默认构造函数
~MyClass(); //默认析构函数
private:
int m_num;
int* m_ptr;
};
在上面的示例中,如果没有显式定义Myclass的构造函数和析构函数,编译器会提供默认实现。可以通过以下方式来显式定义默认构造函数和析构函数:
MyClass::MyClass(){
//默认构造函数的实现
}
MyClass::~MyClass(){
//默认析构函数的实现
}
说明:
- 对没有定义构造函数的类,其公有数据成员可以用初始化参数列表进行初始化
class MyClass{
public:
char name[10];
int age;
};
MyClass my= {"chen", 23};
cout << my.name << my.age << endl;
构造函数可以重载,也就是在一个类中可以定义多个不同形式的构造函数,用于创建对象是提供不同的参数选择。在创建对象时,系统会根据传递的参数类型和数量来选择调用哪一个构造函数。
例如:下面是一个定义了两个构造函数的类示例:
class MyClass{
public:
MyClass(){
//无参构造函数
}
MyClass(int value){
//带有一个int类型参数的构造函数
m_value = value;
}
private:
int m_value;
};