C++类与对象(初始化列表,explicit关键字,static成员,缺省声明,友元,内部类)

145 阅读4分钟

本文已参与新人创作礼活动,一起开启掘金创作之路。

一、初始化列表

构造函数除了以普通的函数方式存在之外,也可以以初始化列表的形式存在。 初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个成员变量后面跟一个放在括号中的初始值或表达式。

1.与函数形式的构造函数对比

Data(int year,int month,int day)
{
 _year=year;
 _month=month;
 _day=day;
}

这是之前实现的一种构造函数。

Data(int year, int month, int day)
        :_year(year)
        ,_month(month)
        ,_day(day)
    {}

这是初始化类表版本的构造函数。 注意:初始化列表是成员变量定义的地方,相当于全部在定义的时候就已经初始化了。

2.初始化列表的优势

在定义构造函数时,我们建议使用初始化列表形式来定义构造函数,因为在一些特殊情况下,函数形式是无法进行初始化的。

class Data
{
private:
        int _year;
        int _month;
        int _day;
        const int _n;
public:
    Data(int year, int month, int day,const int n)
    {
        _year = year;
        _month = month;
        _day = day;
        _n = n;
    }
};
int main()
{
    Data d1(2022, 1, 1,6);
}

当成员变量具有常属性的时候,不能像如上进行初始化,因为常属性的变量必须在定义的时候被初始化。私有类型中只是对其进行的一个声明,这时候我们发现就没有机会对其进行初始化了。 而在初始化列表就是成员变量被定义的地方,所以使用初始化列表版本的构造函数就可以对常属性的变量进行初始化了。

class Data
{
private:
        int _year;
        int _month;
        int _day;
        const int _n;
public:
    Data(int year, int month, int day)
        :_year(year)
        ,_month(month)
        ,_day(day)
        ,_n(1)
    {}
};
int main()
{
    Data d1(2022, 1, 1);
}

初始化列表在定义的时候就可以直接给常属性变量进行赋值,因为初始化列表是成员变量定义的地方,常属性变量只能在其被定义的地方初始化。

3.两种定义方式的本质区别

d1中的成员变量都是在定义类的时候定义完了,但是第一种方式相当于只是定义了成员变量,需要调用函数才能对成员变量进行初始化。而第二种方式相当于在定义类的时候就已经将它们初始化了。如果只想在定义类的时候对常属性的变量进行初始化,我们还可以这样写:

Data(int year, int month, int day)
        :_n(10)
    {
        _year = year;
        _month = month;
        _day = day;
    }

所以说初始化列表其实是包含了第一种定义构造函数的情况的。 </font color=red>其实成员变量的定义都发生在初始化列表中,只不过第一种没写也就没进行初始化而已

4.三种使用初始化列表的情况

1.当有常属性成员变量时。 2.当有引用定义的成员时。 3.当有没有默认构造函数的自定义类型成员变量时。

class A
{
private: int _b;
public:A(int a)
{
    _b = a;
}
};
class Data
{
private:
        const int _n;
        int& _ret;
        A _a;
public:
    Data(int a)
        :_n(10)
        ,_ret(a)
        ,_a(10)
    {}
};
int main()
{
    int i = 0;
    Data d1(i);
}

这三种情况使用初始化列表初始化如上,但是要注意,ret并不是i的引用而是a的引用,改变i的值无法改变ret的值,如果想改变应该使用引用传参。

5.初始化顺序

成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关。

class A
{
private: int _a2;
         int _a1;
public:A(int a)
    :_a1(1)
    ,_a2(_a1)
{}
};
int main()
{
    A a(1);
}

在这里插入图片描述

比如上面这段代码,执行之后a1被赋值成了1,但是a2却被赋值成了随机值。 这是因为a2的声明比a1早,所以先初始化的是a2,然后初始化的a1。

二、explicit关键字

1.对象的隐式类型转换

在定义类的时候也会发生强制类型的转换。

class Data
{
private:
    int _year;
    /*int _month;
    int _day;*/
public:
    Data(int year)
        :_year(year)
    /*  ,_month(month)
        ,_day(day)*/
    {}
};
int main()
{
    Data d1(2022);
    d1 = 10;
}

</font color=blue>这里是一个隐式类型转换,这里的意思就是首先用10构造一个临时对象,再用这个对象拷贝构造d1。 虽然一共发生了两次拷贝构造,但是只调用了一次拷贝构造函数,这是编译器为了方便而做的优化。

我们发现这里一共调用了两次函数,一个是构造函数,一个是拷贝构造,编译器优化的时候把拷贝构造给优化掉了。

2.explicit

expilcit可以阻止对象的隐式类型转换的发生。

explicit Data(int year)
        :_year(year)
    {}

这样当再将d1=20时,编译器就会发生报错。