🌟hello,各位读者大大们你们好呀🌟
🍭🍭系列专栏:【C++学习与应用】
✒️✒️本篇内容:初始化列表的概念和使用注意事项,explicit关键字的应用,static成员的概念和特征
🚢🚢作者简介:计算机海洋的新进船长一枚,请多多指教( •̀֊•́ ) ̖́-
目录
(1)每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
(4)成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关
一、再谈构造函数
在创建对象时,编译器通过调用构造函数,给对象中各个成员变量一个合适的初始值。
class Date
{
public:
Date(int year = 2022, int month = 11, int day = 27)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
虽然上述构造函数调用之后,对象中已经有了一个初始值,但是不能将其称为对对象中成员变量的初始化,构造函数体中的语句只能将其称为赋初值,而不能称作初始化。因为初始化只能初始化一次,而构造函数体内可以多次赋值。
二、初始化列表
1.初始化列表的概念
实际上,初始化列表的知识属于构造函数知识的一部分。顾名思义,初始化列表就是为了解决对象初始化问题而存在的。
初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式。
我们可以将上面的代码改成如下形式进行初始化。
class Date
{
public:
Date(int year = 2022, int month = 11, int day = 27)
: _year(year)
, _month(month)
, _day(day)
{}
private:
int _year;
int _month;
int _day;
};
2.初始化列表的注意事项
(1)每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
多次初始化会报错
class Date
{
public:
//构造函数
Date(int year = 2022, int month = 4, int day = 19)
:_year(year)
, _month(month)
, _day(day)
, _month(month) //初始化列表多次初始化,err
{}
private:
int _year;
int _month;
int _day;
};
编译器也允许构造函数赋初值和初始化列表初始化混用,混用时初始化列表初始化和构造函数赋初值不冲突
class Date
{
public:
//构造函数
Date(int year = 2022, int month = 11, int day = 27)
: _year(year) //两者不冲突
, _month(month)
{
_day = day;
_year = 2023;
}
private:
int _year;
int _month;
int _day;
};
(2)类中包含以下成员,必须放在初始化列表位置进行初始化:
- 引用成员变量
- const成员变量
- 自定义类型成员(且该类没有默认构造函数时)
const成员变量、引用成员变量、没有默认构造函数的自定义类型成员变量必须在初始化列表内初始化的原因:
①初始化列表是对象的成员变量定义的地方。
②对象的内置类型成员变量在初始化列表定义时没有要求必须初始化,因此既可以在初始化列表进行初始化,也可以在构造函数体内初始化。
③而const成员变量、引用成员变量、没有默认构造函数的自定义类型成员变量,不能先定义再初始化,它们在初始化列表内定义,并且必须在定义时就初始化,因此必须在初始化列表内初始化。
引用成员变量要在初始化列表初始化
class Date
{
public:
//构造函数
Date(int year = 2022, int month = 11, int day = 27)
: _year(year)
, _month(month)
,_ref(year)//引用成员变量要在初始化列表初始化
{
_day = day;
//_ref = year; //引用成员变量不能在函数体内初始化
}
private:
int _year;
int _month;
int _day;
int& _ref;
};
const成员变量必须使用初始化列表进行初始化
class Date
{
public:
//构造函数
Date(int year = 2022, int month = 11, int day = 27)
: _year(year)
, _month(month)
, _n(2) //const成员变量必须使用初始化列表进行初始化
{
_day = day;
//_n = 2; //const成员变量不能在函数体内初始化
}
private:
int _year;
int _month;
int _day;
const int _n = 1;
};
没有默认构造函数的自定义类型成员变量,必须在初始化列表进行初始化
#include <iostream>
using namespace std;
class A
{
public:
//默认构造函数是不用传参就可以调用的构造函数,有3种:
//1.无参默认构造函数
//2.带参全缺省的默认构造函数(每个变量都有赋值)
//3.我们不写,编译器自动生成的默认构造函数
A(int x)//不属于以上任何一种,所以A类的对象没有默认构造函数
{
cout << "A(int x)" << endl;
_x = x;
}
private:
int _x;
};
class Date
{
public:
//构造函数
Date(int year = 2022, int month = 11, int day = 27)
: _year(year)
, _month(month)
, _a(20)//没有默认构造函数的自定义类型成员变量必须在初始化列表进行初始化
{
_day = day;
}
private:
int _year;
int _month;
int _day;
A _a;
};
(3)尽量使用初始化列表初始化
因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化。
class Time
{
public:
Time(int hour = 0)
:_hour(hour)
{
cout << "Time()" << endl;
}
private:
int _hour;
};
class Date
{
public:
Date(int day)
{}
private:
int _day;
Time _t;
};
int main()
{
Date d(1);
}
(4)成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关
也就是说,类中(private:)声明变量的顺序是什么,初始化的顺序就是什么
我们来看一下下面这个代码题加深理解:
- A.输出1 1
- B.程序崩溃
- C.编译不通过
- D.输出1 随机值
class A
{
public:
A(int a)
:_a1(a)
, _a2(_a1)
{}
void Print() {
cout << _a1 << " " << _a2 << endl;
}
private:
int _a2;
int _a1;
};
int main() {
A aa(1);
aa.Print();
}
答案如下,原因:_a2先初始化,此时_a1尚未初始化成1,因此_a2初始化为随机值
3.explicit关键字
构造函数不仅可以构造与初始化对象,对于单个参数或者除第一个参数无默认值其余均有默认值的构造函数,还具有类型转换的作用。
我们先看一下类型转换,可以做到什么
void Test()
{
Date d1(2022);
// 用一个整形变量给日期类型对象赋值
// 实际编译器背后会用2023构造一个无名对象,最后用无名对象给d1对象进行赋值
d1 = 2023;
}
将1屏蔽掉,2放开时则编译失败,因为explicit修饰构造函数,禁止了单参构造函数类型转换的作用
class Date
{
public:
// 1. 单参构造函数,没有使用explicit修饰,具有类型转换作用
// explicit修饰构造函数,禁止类型转换---explicit去掉之后,代码可以通过编译
// explicit Date(int year)
Date(int year)
:_year(year)
{}
/*
// 2. 虽然有多个参数,但是创建对象时后两个参数可以不传递,没有使用explicit修饰,具
有类型转换作用
// explicit修饰构造函数,禁止类型转换
explicit Date(int year, int month = 1, int day = 1)
: _year(year)
, _month(month)
, _day(day)
{}
*/
Date& operator=(const Date& d)
{
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
private:
int _year;
int _month;
int _day;
};
二、static成员(静态成员变量 & 静态成员函数)
1.概念
声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用static修饰的成员函数,称之为静态成员函数。
【注意】静态成员变量一定要在类外进行初始化
2.特征
- 静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区
- 静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明
- 类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问
- 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
- 静态成员也是类的成员,受public、protected、private 访问限定符的限制
第1点很好理解,就是同一个类的不同对象都可以使用同一个静态成员,(类未被清理之前)静态成员使用完之后不会销毁;第2、3、4点我们直接通过代码来理解;第5点很简单,这里就不再赘述了;
下面的代码有点长,兄弟们大致看看
class A
{
public:
A(int a = 0)//构造
:_a(a)
{
++N;
}
A(const A& aa)//拷贝构造
:_a(aa._a)
{
++N;
}
// 静态成员函数没有this指针,只能访问静态成员(特征第4点)
static int GetN()
{
return N;
}
private:
int _a;
static int N; // 声明
};
// 生命周期是全局的,作用域受类域限制,也就是说N的访问受类的限制
int A::N = 0; // 类外,定义初始化(特征第2点)
void F1(A aa)
{}
A F2()
{
A aa;
return aa;//返回不是直接返回,需要调用一次拷贝构造,再把构造值返回
}
int main()
{
//静态变量的返回方法
/* A aa1(1);
A aa2 = 2;
A aa3 = aa1;
cout << aa1.GetN() << endl;//访问静态成员函数(特征第3点)
F1(aa1);
cout << aa1.GetN() << endl;
*/
//类的静态变量不能在main()直接修改
// --N;
F2();
//静态函数的返回方法
//这里有一个访问静态成员函数的优化技巧
//优化前
//A aa4;
//cout << aa4.GetN() << endl;
//优化后
cout << A::GetN() << endl;
return 0;
}
【问题】
1. 静态成员函数可以调用非静态成员函数吗?
答:不可以
2. 非静态成员函数可以调用类的静态成员函数吗?
答:可以
🌹🌹初始化列表、static成员的知识大概就讲到这里啦,博主后续会继续更新更多C++的相关知识,干货满满,如果觉得博主写的还不错的话,希望各位小伙伴不要吝啬手中的三连哦!你们的支持是博主坚持创作的动力!💪💪