我们上次说到了,构造函数是为了初始化我们的对象,而非在内存中开辟一个对象。而默认构造函数的特点是对内置类型不做任何的处理,对自定义类型而言,会去调用它的默认构造函数。
那如果自定义类型成员也没有默认构造函数呢?比如说它是半缺省的构造函数。 这时候编译器就会报错,因为没有合适的默认构造函数可以调用。
class Time
{
public:
Time(int hour, int min) //自己实现一个非默认的构造函数
{
_hour = hour;
_min = min;
}
private:
int _hour;
int _min;
};
class Date
{
public://Date没有默认的构造函数
private:
int _year;
int _month;
int _day;
Time _t;
};
int main()
{
Date d1;
return 0;
}
一、初始化列表
一种解决方法是,在Date中实现非默认的构造函数,在里面对_t的成员进行初始化,但是这样避免不了要先构造一个新的Time类型的成员,然后对_t进行拷贝构造。
但是这样太麻烦了,C++为了解决这种情况,引入了初始化列表!我来为大家写一个初始化列表,再仔细地介绍它的写法。
class Time
{
public:
Time(int hour, int min) //自己实现一个非默认的构造函数
{
_hour = hour;
_min = min;
}
private:
int _hour;
int _min;
};
class Date
{
public:
Date()
:_year(1)
,_month(2)
,_t(1,2)
{
}
private:
int _year;
int _month;
int _day;
Time _t;
};
int main()
{
Date d1;
return 0;
}
初始化列表在构造函数处,以冒号开始,每个成员变量的后面跟上一个括号,括号内是一个表达式或者数字。
我们可以认为初始化列表是成员变量定义的地方,即使成员变量没有出现在初始化列表中,我们也要经过初始化列表这样过程。
如果我们在构造函数的内部对成员变量赋上另外的值之后,那刚开始初始化的值就会被改变,这是因为初始化列表仅仅只是赋上初值,对象的值还是可以改变的。
简单地介绍完初始化列表后,我们自然要讲一下它值得注意的一些小点,首先来看一下下面这道题,为了方便,我们直接把成员变量放在public。
class Date
{
public:
Date()
:_month(1)
,_year(_month)
{
}
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
cout<<d1._month<<" "<<d1._year<<endl;//输出的结果是什么?
return 0;
}
很多人会觉得答案很显然是1 1,但并不是,答案是1 和随机值。
原因是什么呢?其实是初始化列表的顺序搞的鬼!
初始化列表的初始化顺序与其声明的顺序有关!
我们可以看出来_year是首先被声明的,所以它在初始化列表中也首先被初始化,但此时_month还是一个随机值,所以说_year也被初始化为随机值,但_month仍然是正常的初始化。
为了保证自定义类型的成员能够很好的被初始化,作者的建议是最好都写初始化列表,但并不是绝对的,有时候在成员里有类似数组的成员时,往往在函数体内的构造比较好用!因为在初始化列表的小括号中写比较繁琐而且可读性并不是很好的。
除了无默认构造函数的自定义成员,对于const修饰的成员,以及引用成员都需要在初始化列表中初始化,因为后面的两种都需要在声明的时候初始化。
为了引入下面要讲的内容,我们先来看一下这个代码是否是正确的?
二、explicit关键字
class Date
{
public:
Date(int year)
{
_year = year;
}
int _year;
int _month;
int _day;
};
int main()
{
Date d1(1);
Date d2 = 2;
return 0;
}
d1的初始化显然是正确的,那d2呢?左侧的类型是Date,而右侧是int类型,这显然会产生一个隐式的类型转化。
在这里,作者需要介绍一下关于隐式类型转化的原理,实际上隐式类型转化是用右侧的值拷贝一个同类型的,然后用这个拷贝转换为左侧类型,它并不会对右侧的数据做任何修改,而是对拷贝对象的修改。上述d2处的转换是可以的,这不仅是由于隐式转换的可行性,另一个原因是其构造函数是单参数的,如果是多参数,那可就不行了哦!
那么这种的情况是否可以呢?这次是的类型是Date&的。
class Date
{
public:
Date(int year)
{
_year = year;
}
int _year;
int _month;
int _day;
};
int main()
{
Date& d2 = 2;
return 0;
}
答案是否定的,原因是拷贝的对象是具有常属性的,C++是不支持对常量进行引用的。除非加上const修饰。
既然已经讲到隐式类型转换了,那么如何避免这种隐式类型的转换呢?C++引入了explicit关键字,将构造函数进行修饰,就可以防止这种情况。
三、static成员
咱们来看一下这么一道题。
初看其实挺懵逼的,什么都不能用,几乎将能用的计算方法都规避掉了。这里提供一种十分妙的方法,然后作者将会改进这种方法,从而引入我们新的知识点。
我们可以对象每次创建都会调用默认构造函数的特点,定义一个Sum类,实现一个默认构造函数,里面实现加法。再构造一个具有n个元素为Sum对象的数组,这样数组初始化的时候,会对每一个Sum对象进行初始化,就会调用n次的构造函数,这就实现了对于循环的控制。
int sum = 0;
int i = 1;
class Sum
{
public:
Sum()
{
sum += i;
i++;
}
};
class Solution {
public:
int Sum_Solution(int n) {
Sum a[n];
return sum;
}
};
但是对于全局的变量,往往不是很安全,我们想的是能不能拥有一个成员变量为Sum类所用呢?并且它具有全局变量的静态属性。
为了解决这种问题,C++引入了static修饰的成员。static修饰的成员具有静态属性,它就和普通意义上的静态变量没什么区别,只是它也受类的访问限定符限制。
static修饰的成员变量是为所有的对象共有的,换句话说是一模一样的。
有一点要注意,static修饰的成员变量必须在类外进行初始化。
有了static,我们就可以对上面的代码进行改进了。
class Sum
{
public:
Sum()
{
_sum += _i;
_i++;
}
int GetSum()
{
return _sum;
}
private:
static int _sum;
static int _i;
};
int Sum::_sum = 0;
int Sum::_i = 1;
class Solution {
public:
int Sum_Solution(int n) {
Sum a[n];
return a[0].GetSum();
}
};
为了得到最后的结果,我们可以将静态成员放在public中,也可以实现一个GetSum函数来得到。
当然,Static不仅可以修饰变量,也可以修饰成员函数,被Static修饰的成员函数不具有隐藏的this指针。
所以说,静态的成员函数是不能访问非静态的成员变量的,也不能访问非静态的成员函数。
由static修饰的函数在类外,只需要加上类域限定符::就可以访问。所以代码还可以写成这样。
class Sum
{
public:
Sum()
{
_sum += _i;
_i++;
}
static int GetSum()
{
return _sum;
}
private:
static int _sum;
static int _i;
};
int Sum::_sum = 0;
int Sum::_i = 1;
class Solution {
public:
int Sum_Solution(int n) {
Sum a[n];
return Sum::GetSum();
}
};