13. C++构造函数,初始化列表,析构函数

C++构造函数,初始化列表,析构函数

#include <iostream>
using namespace std;

class Student
{
private:
    char *m_name;
    int m_age;
    float m_score;
public:
    //声明构造函数
    Student(char *name, int age, float score);
    //声明普通成员函数
    void show();
};

//定义构造函数
Student::Student(char *name, int age, float score)
{
    m_name = name;
    m_age = age;
    m_score = score;
}
//定义普通成员函数
void Student::show(){
    cout<<m_name<<"的年龄是"<<m_age<<",成绩是"<<m_score<<endl;
}

int main()
{
    //创建对象时向构造函数传参
    Student stu("小明", 15, 92.5f);
    stu.show();
    //创建对象时向构造函数传参
    Student *pstu = new Student("李华", 16, 96);
    pstu -> show();

    return 0;
}
  1. 如果用户自己没有定义构造函数,那么编译器会自动生成一个默认的构造函数,只是这个构造函数的函数体是空的,也没有形参,也不执行任何操作。比如上面的 Student 类,默认生成的构造函数如下:
    Student(){}
  2. 最后需要注意的一点是,调用没有参数的构造函数也可以省略括号。对于示例2的代码,创建对象可以写作Student stu()或Student stu,创建对象指针可以写作Student *pstu = new Student()或Student *pstu = new Student,它们都会调用构造函数 Student()。

C++构造函数的初始化列表使得代码更加简洁,请看下面的例子:

#include <iostream>
using namespace std;
class Student{
private:
    char *m_name;
    int m_age;
    float m_score;
public:
    Student(char *name, int age, float score);
    void show();
};

//采用初始化列表
Student::Student(char *name, int age, float score): m_name(name), m_age(age), m_score(score)
{
    //TODO:
}
void Student::show(){
    cout<<m_name<<"的年龄是"<<m_age<<",成绩是"<<m_score<<endl;
}

int main(){
    Student stu("小明", 15, 92.5f);
    stu.show();
    Student *pstu = new Student("李华", 16, 96);
    pstu -> show();

    return 0;
}

使用构造函数初始化列表并没有效率上的优势,仅仅是书写方便,尤其是成员变量较多时,这种写法非常简单明了。
注意,成员变量的初始化顺序与初始化列表中列出的变量的顺序无关,它只与成员变量在类中声明的顺序有关。

请看代码:

#include <iostream>
using namespace std;

class Demo{
private:
    int m_a;
    int m_b;
public:
    Demo(int b);
    void show();
};

Demo::Demo(int b): m_b(b), m_a(m_b)
{

 }
void Demo::show()
{ 
    cout<<m_a<<", "<<m_b<<endl; 
}

int main(){
    Demo obj(100);
    obj.show();
    return 0;
}

在初始化列表中,我们将 m_b 放在了 m_a 的前面,看起来是先给 m_b 赋值,再给 m_a 赋值,其实不然!成员变量的赋值顺序由它们在类中的声明顺序决定,在 Demo 类中,我们先声明的 m_a,再声明的 m_b,所以构造函数和下面的代码等价:

Demo::Demo(int b)
{
    m_a = m_b;
    m_b = b;
}

给 m_a 赋值时,m_b 还未被初始化,它的值是不确定的,所以输出的 m_a 的值是一个奇怪的数字;给 m_a 赋值完成后才给 m_b 赋值,此时 m_b 的值才是 100。
obj 在栈上分配内存,成员变量的初始值是不确定的。
构造函数初始化列表还有一个很重要的作用,那就是初始化 const 成员变量。初始化 const 成员变量的唯一方法就是使用初始化列表。例如 VS/VC 不支持变长数组(数组长度不能是变量),我们自己定义了一个 VLA 类,用于模拟变长数组,请看下面的代码:

class VLA
{
private:
    const int m_len;
    int *m_arr;
public:
    VLA(int len);
};

//必须使用初始化列表来初始化 m_len
VLA::VLA(int len): m_len(len)
{
    m_arr = new int[len];
}

析构函数没有参数,不能被重载,因此一个类只能有一个析构函数。如果用户没有定义,编译器会自动生成一个默认的析构函数。
== VLA 类来模拟变长数组,它使用一个构造函数为数组分配内存,这些内存在数组被销毁后不会自动释放,所以非常有必要再添加一个析构函数,专门用来释放已经分配的内存。==
请看下面的完整示例:

#include <iostream>
using namespace std;

class VLA
{
public:
    VLA(int len);  //构造函数
    ~VLA();  //析构函数
public:
    void input();  //从控制台输入数组元素
    void show();  //显示数组元素
private:
    int *at(int i);  //获取第i个元素的指针
private:
    const int m_len;  //数组长度
    int *m_arr; //数组指针
    int *m_p;  //指向数组第i个元素的指针
};

VLA::VLA(int len): m_len(len)
{  //使用初始化列表来给 m_len 赋值
    if(len > 0){ m_arr = new int[len];  /*分配内存*/ }
    else{ m_arr = NULL; }
}
VLA::~VLA(){
    delete[] m_arr;  //释放内存
}
void VLA::input(){
    for(int i=0; m_p=at(i); i++){ cin>>*at(i); }
}
void VLA::show(){
    for(int i=0; m_p=at(i); i++){
        if(i == m_len - 1){ cout<<*at(i)<<endl; }
        else{ cout<<*at(i)<<", "; }
    }
}
int * VLA::at(int i){
    if(!m_arr || i<0 || i>=m_len){ return NULL; }
    else{ return m_arr + i; }
}

int main()
{
    //创建一个有n个元素的数组(对象)
    int n;
    cout<<"Input array length: ";
    cin>>n;
    VLA *parr = new VLA(n);
    //输入数组元素
    cout<<"Input "<<n<<" numbers: ";
    parr -> input();
    //输出数组元素
    cout<<"Elements: ";
    parr -> show();
    //删除数组(对象)
    delete parr;
    return 0;
}

~VLA()就是 VLA 类的析构函数,它的唯一作用就是在删除对象(第 53 行代码)后释放已经分配的内存。C++中的 new 和 delete 分别用来分配和释放内存,它们与C语言中 malloc()、free() 最大的一个不同之处在于:用 new 分配内存时会调用构造函数,用 delete 释放内存时会调用析构函数。构造函数和析构函数对于类来说是不可或缺的,所以在C++中我们非常鼓励使用 new 和 delete

posted @ 2024-02-04 15:00  最想要去的地方  阅读(6)  评论(0编辑  收藏  举报