c++ 构造函数与析构函数

本文参考菜鸟教程,仅作笔记用。

构造函数

构造函数(Constructor)是一种特殊的方法,用于在创建对象时进行初始化操作。构造函数的名称与类的名称是完全相同的,并且不会返回任何类型,也不会返回 void。在面向对象编程中,通常每个类都可以有一个构造函数。构造函数的作用是初始化对象的状态,为对象的属性赋予初始值,以确保对象在创建后处于一个合适的状态。

在继承关系中,子类可以继承父类的属性和方法,但是构造函数却不能直接继承。这是因为构造函数的特殊性质,它在对象创建时被调用,用于初始化该对象的状态。当子类继承父类时,子类可以使用父类的属性和方法,但是子类的构造函数需要负责初始化子类自己的属性,而不能直接继承父类的构造函数。

通常情况下,如果子类没有定义自己的构造函数,那么会隐式地调用父类的构造函数来初始化子类的实例但是如果子类定义了自己的构造函数,那么子类的构造函数就会覆盖父类的构造函数,这样父类的构造函数就无法被子类直接继承和使用了。

cpp 复制代码
#include <iostream>
 
using namespace std;
 
class Line
{
   public:
      void setLength( double len );
      double getLength( void );
      Line();  // 这是构造函数
 
   private:
      double length;
};
 
// 成员函数定义,包括构造函数
Line::Line(void)
{
    cout << "Object is being created" << 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;
}
<<  Object is being created
<<  Length of line : 6

带参数的构造函数

cpp 复制代码
#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;
}
<< Object is being created, length = 10
<< Length of line : 10
<< Length of line : 6

使用初始化列表来初始化字段

初始化列表允许我们在构造函数中以更加直接和高效的方式初始化类的成员变量,特别是对于常量或者引用类型的成员变量尤其有用。

使用初始化列表来初始化字段:

cpp 复制代码
Line::Line( double len): length(len)
{
    cout << "Object is being created, length = " << len << endl;
}

上面的语法等同于如下语法:

cpp 复制代码
Line::Line( double len)
{
    length = len;
    cout << "Object is being created, length = " << len << endl;
}

再举个例子

cpp 复制代码
#include <iostream>
#include <string>

class Car {
private:
    std::string& owner;  // Reference member variable
    std::string model;

public:
    // Constructor with initialization list
    Car(std::string& o, const std::string& m) 
        : owner(o), model(m) {
        // Constructor body (if needed)
    }

    void displayInfo() const {
        std::cout << "Owner: " << owner << ", Model: " << model << std::endl;
    }
};

int main() {
    std::string alice = "Alice";
    std::string bob = "Bob";

    // Initializing objects of class Car using initialization list
    Car car1(alice, "Toyota");
    car1.displayInfo();

    Car car2(bob, "Honda");
    car2.displayInfo();

    return 0;
}
<< Owner: Alice, Model: Toyota
<< Owner: Bob, Model: Honda

析构函数

类的析构函数是类的一种特殊的成员函数,它会在每次删除所创建的对象时执行。

析构函数的名称与类的名称是完全相同的,只是在前面加了个波浪号(~)作为前缀,它不会返回任何值,也不能带有任何参数。析构函数有助于在跳出程序(比如关闭文件、释放内存等)前释放资源。

子类会继承父类的析构函数,但并不是通过继承其实现。当子类的对象销毁时,会先调用子类的析构函数,然后再调用父类的析构函数。这种顺序确保了对象的析构顺序与构造顺序相反。

如果父类有虚析构函数(通过在析构函数前面加上 virtual关键字),那么子类也会继承这个虚析构函数。这是为了实现多态性,确保在使用基类指针指向派生类对象时正确地释放资源。

加上 virtual 关键字确实是声明虚函数的方式,但在析构函数前面加上 virtual关键字有特定的作用。这种做法是为了确保正确的析构函数调用顺序和资源释放。

当你有一个基类指针指向派生类对象时,如果基类的析构函数是虚函数(即声明为virtual),那么在销毁这个对象时,会根据实际对象的类型(基类或派生类)来调用适当的析构函数。这种机制称为动态绑定或者多态性。

具体来说,如果基类的析构函数不声明为虚函数,那么当你通过基类指针删除一个派生类对象时,只会调用基类的析构函数,而不会调用派生类的析构函数。这可能导致派生类对象中的资源(如动态分配的内存)没有被正确释放,从而产生内存泄漏或其他问题。

因此,为了确保在继承体系中正确地释放资源,通常建议在基类的析构函数前面加上 virtual关键字,以启用动态绑定机制。这样,无论通过基类指针如何删除对象,都能正确调用对象的实际类型的析构函数,确保资源的正确释放。

总结一下,加上 virtual 关键字在析构函数中的作用是确保在继承体系中正确地调用对象的析构函数,从而实现多态性和正确的资源管理。

cpp 复制代码
#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;
}
<< Object is being created
<< Length of line : 6
<< Object is being deleted

注意

初始化顺序最好要按照变量在类声明的顺序一致,否则会出现下面的特殊情况:

cpp 复制代码
#include<iostream>
 
using namespace std;

class Student1 {
    public:
        int a;
        int b;
        void fprint(){
            cout<<" a = "<<a<<" "<<"b = "<<b<<endl;
         }
         
         Student1(int i):b(i),a(b){ }    //异常顺序:发现a的值为0  b的值为2  说明初始化仅仅对b有效果,对a没有起到初始化作用 
//         Student1(int i):a(i),b(a){ } //正常顺序:发现a = b = 2 说明两个变量都是初始化了的  

         Student1()                         // 无参构造函数
        { 
            cout << "默认构造函数Student1" << endl ;
        }
    
        Student1(const Student1& t1) // 拷贝构造函数
        {
            cout << "拷贝构造函数Student1" << endl ;
            this->a = t1.a ;
        }
    
        Student1& operator = (const Student1& t1) // 赋值运算符
        {
            cout << "赋值函数Student1" << endl ;
            this->a = t1.a ;
            return *this;
        }
      
 };
class Student2
{
    public:
     
    Student1 test ;
    Student2(Student1 &t1){
        test  = t1 ;
    }
//     Student2(Student1 &t1):test(t1){}
};
int main()
{
    
    Student1 A(2);        //进入默认构造函数 
    Student2 B(A);        //进入拷贝构造函数 
    A.fprint();            //输出前面初始化的结果 
}


一个类内可以有多个构造函数,可以是一般类型的,也可以是带参数的,相当于重载构造函数,但是析构函数只能有一个

cpp 复制代码
class Matrix
{
public:
    Matrix(int row, int col);                                            //普通构造函数
    Matrix(const Matrix& matrix);                                        //拷贝构造函数
    Matrix();                                                            //构造空矩阵的构造函数
    void print(void);
    ~Matrix();
};

总结上述两点注意事项,代码如下:

cpp 复制代码
#include<iostream>

using namespace std;

class Student1 {
public:
    int a=0;
    int b=0;

    void fprint() {
        cout << " a = " << a << " " << "b = " << b << "\n"<<endl;
    }

    Student1()
    {
        cout << "无参构造函数Student1" << endl;
    }

    Student1(int i):a(i),b(a)
    { 
        cout << "有参参构造函数Student1" << endl;
    } 

    Student1(const Student1& t1)
    {
        cout << "拷贝构造函数Student1" << endl;
        this->a = t1.a;
        this->b = t1.b;
    }

    Student1& operator = (const Student1& t1) // 重载赋值运算符
    {
        cout << "赋值函数Student1" << endl;
        this->a = t1.a;
        this->b = t1.b;
        return *this;
    }

};
class Student2
{
public:

    Student1 test;
    Student2(Student1& t1) {
        t1.fprint();
        cout << "D: ";
        test = t1;
    }
    //     Student2(Student1 &t1):test(t1){}
};
int main()
{
    cout << "A: ";
    Student1 A;
    A.fprint();

    cout << "B: ";
    Student1 B(2); 
    B.fprint();

    cout << "C: ";
    Student1 C(B); 
    C.fprint(); 

    cout << "D: ";
    Student2 D(C);
    D.test.fprint();
}

/*A: 无参构造函数Student1
 a = 0 b = 0

B: 有参参构造函数Student1
 a = 2 b = 2

C:拷贝构造函数Student1
 a = 2 b = 2

D: 无参构造函数Student1
 a = 2 b = 2

D: 赋值函数Student1
 a = 2 b = 2
相关推荐
DreamByte4 分钟前
Python Tkinter小程序
开发语言·python·小程序
覆水难收呀12 分钟前
三、(JS)JS中常见的表单事件
开发语言·前端·javascript
阿华的代码王国16 分钟前
【JavaEE】多线程编程引入——认识Thread类
java·开发语言·数据结构·mysql·java-ee
繁依Fanyi22 分钟前
828 华为云征文|华为 Flexus 云服务器部署 RustDesk Server,打造自己的远程桌面服务器
运维·服务器·开发语言·人工智能·pytorch·华为·华为云
weixin_4866811438 分钟前
C++系列-STL容器中统计算法count, count_if
开发语言·c++·算法
基德爆肝c语言38 分钟前
C++入门
开发语言·c++
怀九日1 小时前
C++(学习)2024.9.18
开发语言·c++·学习·面向对象·引用·
一道秘制的小菜1 小时前
C++第七节课 运算符重载
服务器·开发语言·c++·学习·算法
易辰君1 小时前
Python编程 - 协程
开发语言·python
布洛芬颗粒1 小时前
JAVA基础面试题(第二十二篇)MYSQL---锁、分库分表!
java·开发语言·mysql