初始化列表
初始化列表是构造函数的一种方式,使用初始化列表可以解决一些构造函数体赋值难以解决的问题
首先先再次认识一下构造函数
构造函数
1.构造函数体赋值
构造函数体赋值就是,我们先前最为常用的方法
class Date { public: Date(int year, int month, int day)//只是在函数体中赋值 { _year = year; _month = month; //{} 这是函数体 _day = day; } private: int _year; int _month; int _day; }; int main() { Date d(2023, 5, 12);//我们要知道的是,调用构造函数之后,对象d有了一个初始值,但是不能成为对对象成员变量的初始化,构造函数体中的语句只能将其称为赋初值,而不能称作初始化。因为初始化只能初始化一次,而构造函数体内可以多次赋值。 return 0; }
2.初始化列表
初始化列表:以冒号开始,接着是以逗号分割的数据成员列表,每一个成员变量后面跟一个放在括号中的初始值或表达式。
class Date { public: Date(int year, int month, int day) :_year(year) , _month(month) , _day(day) {} private: int _year; int _month; int _day; }; int main() { Date d(2023, 5, 12); return 0; }
注意
1.每一个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
2.构造函数体赋值,使得对象进行了赋初值,但是不能成为初始化(初始化只能一次,但是函数体里面可以多次赋值)
3. 类中包含以下成员,必须放在初始化列表位置进行初始化:
1.引用成员变量
2.const成员变量
3.自定义类型成员(没有默认构造函数时)
对于上述三种类型,我们只能使用初始化列表来进行初始化
class Date { public: Date(int year, int month, int day) :_year(year) , _month(month) , _day(day) ,a(year) ,b(year) ,c(year) {} private: int _year; int _month; int _day; int& a; const int b; B c; }; class B { public: B(int data) { _data = data; } private: int _data; }; int main() { Date d(2023, 5, 12); return 0; }
对于引用类型,和const类型成员变量,共同特点是只能初始化一次,所以不能放在构造函数体中,只能使用初始化列表来初始化变量
对于自定义类型成员(无默认构造),是因为,当有默认构造的时候,编译器会自动调用自定义类型的默认构造,但是当我们重载构造函数之后,就需要我们手动对于自定义类型进行初始化,(也是只能一次)所以只能在列表中初始化
总之,在这以后,我们尽量多去使用初始化列表来进行初始化,因为初始化列表好比是构造函数体初始化的Plus版本,对于一般类型都能进行初始化,而且可以对于引用类型,const类型、自定义类型都能进行初始化(构造函数体不能实现)
成员变量在类中声明的次序就是其在初始化列表中的初始化次序,与其在初始化列表中的先后次序无关
所以以后写初始化列表的时候,列表顺序保持和声明顺序一致
explicit关键字
在使用构造函数实例化对象的时候,是可以可以通过隐式转换来实例化的
构造函数对于单个参数或者是出去第一个参数无默认值其余均有默认值的构造哈桑怒胡,可以有类型转换的作用(隐式转换)
//隐式转换 class Date { public: //Date(int date) //{ // _date = date; //} void Print() { cout << _date <<" " <<_a<<" " <<_b<< endl; } Date(int date,int a = 2,int b = 3) :_date(date) ,_a(a) ,_b(b) {} private: int _date; int _a; int _b; }; int main() { Date d1(1);//这一种是我们常用的实例化对象的方式 Date d2 = 1;//这个就是涉及到隐式转换 将int类型转换成Date类型,又因为是构造函数是单个参数 d1.Print(); d2.Print(); return 0; }
这一种隐式转换,是对于自定义类型的隐式转换,内置类型的隐式转换在C语言中已经讲解了,所以我们只需要知道自定义类型隐式转换的条件即可
自定义类型隐式转换流程
将内置类型,如本文的int类型转换成Date类型,调用Date构造函数,创建一个临时对象,然后再去调用拷贝构造函数去赋值给d2,按理说,我们应该知道的是,隐式转换是在一条语句中连续两次调用构造函数
class Date { public: //Date(int date) //{ // _date = date; //} void Print() { cout << _date << " " << _a << " " << _b << endl; } Date(int date, int a = 2, int b = 3) :_date(date) , _a(a) , _b(b) { cout << "构造函数" << endl; } Date(const Date& d) { cout << "拷贝构造函数" << endl; } private: int _date; int _a; int _b; }; int main() { Date d1(1);//这一种是我们常用的实例化对象的方式 Date d2 = 1;//这个就是涉及到隐式转换 将int类型转换成Date类型,又因为是构造函数是单个参数 d1.Print(); d2.Print(); return 0; }
对于上述的隐式转换,如果我们不需要这样,避免自定义类型隐式转换,那就使用关键字explicit
使用explicit关键字修饰函数,即可禁止类型转换
class Date{ public: explicit Date(int year) //explicit关键字修饰,禁止隐式类型转换 :_year(year) {} private: int _year; }; int main() {}
隐式类型转换,可读性就不是很好了,所以使用explicit禁止类型转换
static成员
static修饰的成员称为类的静态成员,有静态成员变量or静态成员函数。静态成员变量一定要在类外进行初始化
我们知道自定义类型的初始化方式,使用构造函数的话有两种,第一个是构造函数体初始化,第二个是初始化列表,但是明显不能解决这个static的初始化,因为static修饰的成员变量的生命周期是整个程序
所以解决方法为,使用全局域对于静态变量进行初始化
class Date { public: Date(int year) :_year(year) {} static int get_a() 我们不实例化对象,所以就直接用static修饰,类名调用 { return _a; } private: int _year; static int _a;//public修饰的静态函数/静态变量是可以通过类名直接访问的,但是如果是private的话,是需要类中的函数来访问,所以我们应该定义返回函数 }; int Date::_a = 0; int main() { cout <<Date::get_a() << endl; return 0; }
对于静态static关键字,主要就下面几个方面的细节要注意
1.static修饰的变量声明周期是整个程序,如果是类中的成员变量被static修饰,那么需要在类外进行初始化,因为类中构造函数不能满足需求,且类外(全局域)初始化就一次,放在静态区,对象使用的时候可以直接调用(只是声明周期不一样而已)
2.static修饰的成员变量or成员函数,是可以直接使用类名来调用的
3.访问限定符private修饰的static成员变量,可以通过成员函数来访问,返回
class Date{ public: static int Print() { cout<<a<<endl;} private: static int a; }; int Date::a=0;//类外初始化赋值static变量 int main() { cout<<Date::Print()<<endl; return 0; }
总结
- 静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区
- 静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明
- 类静态成员即可用 **类名::静态成员 或者 对象.静态成员 **来访问
- 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
- 静态成员也是类的成员,受public、protected、private 访问限定符的限制
注意:静态成员函数可以调用静态成员函数,但是不能调用非静态函数,非静态函数两者都能调用
这是因为,static修饰的类方法,是整个类所共有的,不属于任何一个对象,所以就没有this指针(形参中没有this指针),继而无法调用非static修饰的函数(this指针是用来接收当前对象的地址的)
函数都存放在代码段