目录
引入:
在谈初始化列表前,我们先清楚的认识下构造函数。构造函数的定义是在创建对象时,编译器通过调用构造函数,给对象中各个成员变量一个合适的初始值,这里要注意的是,造函数调用之前,对象中已经有了一个初始值。也就是说构造函数体中的语句只能将其称为赋初值,而不能将其称为对象中成员变量的初始化,因为初始化是在建立时给予初值,而构造函数体内可以多次赋值。
在类对象中,对其成员变量进行初始化的操作叫初始化列表,初始化列表是一种用于初始化成员变量的语法结构,它在类的构造函数中使用,用于初始化类的成员变量。总的来说,初始化列表就是类成员变量的初始化。
定义:
初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个 “成员变量” 后面跟一个放在括号中的初始值或表达式。
class Date
{
public:
Date(int year, int month, int day)
//初始化列表,对成员变量进行初始化
:_year(year)
,_month(month)
,_day(day)
{
//...这里进行赋值操作
}
private:
int _year;
int _month;
int _day;
};
初始化列表需注意以下几个要点:
1,每个成员变量在初始化列表中只能出现一次,因为初始化只能初始化一次。
2,类中包含以下成员,必须放在初始化列表位置进行初始化,不能调用构造函数:
(1)引用成员变量。因为引用必须初始化,且一旦初始化后就不能改变,构造函数 等于赋值操作。
(2)const成员变量。const修饰的变量不能改变,与引用成员变量同理。
(3)自定义类型成员(且该类没有默认构造函数时)。自定义类型运用初始化列表,相当 于直接给自定义类型直接赋值。相当于从内置类型转换成自定义类型(后面会详细讲解)。
class A
{
public:
A(int a = 6, int b = 5) //自己定义构造函数,编译器不会自动产生默认构造函数
:_a(a)
, _b(b)
{ /* ....... */ }
private:
int _a;
int _b;
};
class B
{
public:
B(int a = 1, int b = 1)
:_aobj(a) //相当于隐式类型的转换
,_ref(b)
,_n(10)
{ /* ....... */ }
private:
A _aobj; // 没有默认构造函数
int& _ref; // 引用
const int _n; // const
};
int main()
{
B a(5);
return 0;
}
3,因为初始化列表相当于初始化,所以初始化列表在变量定义时就调用。
4,成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关。
class Date
{
public:
Date(int year = 2023, int month = 10, int day = 16)
//在编译器调用时依次调用_year,_month,_day,按照声明顺序调用
: _month(month)
, _day(day)
, _year(year)
{ }
private:
int _year;
int _month;
int _day;
};
这方面也经常会让我们运用,请观看以下代码:
- #include
- using namespace std;
- class A
- {
- public:
- A(int a)
- :_a1(a)
- , _a2(_a1)
- {}
-
- void Print() {
- cout << _a1 << " " << _a2 << endl;//输出1和随机值
- }
- private:
- int _a2;//先调用初始化列表中的_a2
- int _a1;//再调用初始化列表中的_a1
- };
- int main() {
- A aa(1);
- aa.Print();
- }
其实理解起来也非常简单,初始化列表是给变量初始化,在变量声明时就要开始赋值,因此,初始化列表是按照变量声明的顺序进行的。
下面,要说明的是在构造函数中不能只要初始化列表,不要函数体初始化,因为有些初始化或者检查工作,初始化列表也不能全部解决,如下:
class Stack
{
private:
Stack(int capacity = 5)
: _capacity(capacity)
, _top(-1)
{
//a初始化时要进行检查工作,在初始化列表中不能完成
a = new int[capacity];
if (a == nullptr) {
perror("malloc fial");
exit(-1);
}
}
private:
int* a;
int _capacity;
int _top;
};
总:通过以上学习可看出,初始化列表也不能解决全部问题,在后面的学习和深入探索中,初始化列表和构造函数实体往往是混合使用的。
概念:
声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用 static修饰的成员函数,称之为静态成员函数。
注意:
1,静态成员也是类的成员,受public、protected、private访问限定符的限制。
2,静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区,可用sizeof关键字进行查看。
class Date1 //占12个字节
{
int _year;
int _month;
int _day;
static int _a;
};
class Date2 //占16个字节
{
int _year;
int _month;
int _day;
int _a;
};
3,静态成员变量必须在类外进行初始化和定义,定义时不添加static关键字,类中只是声明。因为静态成员没有存储在类中,存储在静态区中,不能对其进行类中的操作。
class Date1
{
public:
Date1(int year = 2023, int month = 10, int day = 16)
: _year(year)
, _month(month)
, _day(day)
//, _a(2)错误,因为静态成员变量没有存储在类中
{ }
int _year;
int _month;
int _day;
static int _a;
};
int Date1::_a = 5;//对静态成员变量进行初始化
4,类静态成员即可用类名::静态成员或者对象.静态成员来访问,但这两种方法访问的权限必须为公有。用类的成员函数也可间接访问,这种方法可间接的在类外访问私有权限的成员。
- #include
- using namespace std;
- class Date1
- {
- public:
- Date1(int year = 2023, int month = 10, int day = 16)
- : _year(year)
- , _month(month)
- , _day(day)
- { }
- int GetData() {
- return _a;
- }
- int _year;
- int _month;
- int _day;
- static int _a;
- };
- int Date1::_a = 5;
- int main()
- {
- Date1 d1;
- cout << d1._a << endl;//权限必须公有
- cout << Date1::_a << endl;//权限必须公有
- cout << d1.GetData() << endl;//即使权限私有也可访问
- return 0;
- }
5,静态成员函数没有隐藏的this指针,不能访问任何非静态成员,但可以访问静态成员,因为访问类中非静态成员需要this指针来 “指引” ,而静态成员存储在静态区中,可以随时访问。
class Date1
{
public:
Date1(int year = 2023, int month = 10, int day = 16)
: _year(year)
, _month(month)
, _day(day)
{ }
static int GetData()
{
//return _year;//没有this指针,找不到此对象
return _a;//_a为静态成员,不在类中,可以访问
}
int _year;
int _month;
int _day;
static int _a;
};
这里要说明的是静态成员函数不可以调用非静态成员函数,但非静态成员函数可以调用类的静态成员函数。静态成员函数由于没有this指针,不能访问类中成员,静态成员函数跟静态成员变量一样,都存储在静态区中,所以可以任意访问。
在C++中,友元是一个关键字,它用于允许一个类的成员函数访问另一个类的私有和保护成员。使用友元函数或友元类,可以访问一个类的私有和保护成员,就像它们是公共成员一样。其中友元共分为:友元函数和友元类。
友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加friend关键字。
class Date
{
//友元函数的声明
friend ostream& operator<<(ostream& _cout, const Date& d);
friend istream& operator>>(istream& _cin, Date& d);
public:
Date(int year = 2023, int month = 10, int day = 16)
: _year(year)
, _month(month)
, _day(day)
{ }
private:
int _year;
int _month;
int _day;
};
//友元函数的定义
ostream& operator<<(ostream& _cout, const Date& d)
{
_cout << d._year << "-" << d._month << "-" << d._day;//访问类中的私有成员变量
return _cout;
}
istream& operator>>(istream& _cin, Date& d)
{
_cin >> d._year >> d._month >> d._day;//访问类中的私有成员变量
return _cin;
}
这里要说明以下几点:
1,友元函数可访问类的私有和保护成员,但不是类的成员函数。
2,友元函数不能用const修饰。
3,友元函数可以在类定义的任何地方声明,不受类访问限定符限制。
在C++中,友元类是一种特殊的关系,它允许一个类访问另一个类的私有和保护成员。使用友元类,可以访问一个类的私有和保护成员,就像它们是公共成员一样。
要声明一个类为另一个类的友元类,可以在类定义中加入friend关键字,并使用类名作为友元类的声明。
class Time
{
// 声明日期类为时间类的友元类,则在日期类中就直接访问Time类中的私有成员变量
friend class Date;
int _hour;
int _minute;
int _second;
};
class Date
{
public:
void SetTimeOfDate(int hour, int minute, int second)
{
// 直接访问时间类私有的成员变量
_t._hour = hour;
_t._minute = minute;
_t._second = second;
}
private:
int _year;
int _month;
int _day;
Time _t;
};
概念:
如果一个类定义在另一个类的内部,那么这个定义在内部的类就叫做内部类。内部类是一个独立的类, 它不属于外部类,更不能通过外部类的对象去访问内部类的成员,即外部类不是内部类的友元,但内部类是外部类的友元类,即内部类可以通过外部类的对象参数来访问外部类中的所有成员。
注意:
1,内部类可以直接访问外部类中的静态成员,不需要外部类的对象/类名。
2,定义内部类时不能直接定义,需要外部类来限定域的作用空间。
- #include
- using namespace std;
- class A
- {
- private:
- static int k;
- int h;
- public:
- class B // B天生就是A的友元
- {
- public:
- void fun(const A& a)
- {
- //这里要说明的是内部类可以直接访问外部类中的静态成员,不需要外部类的对象/类名
- cout << k << endl;//直接访问静态成员
- cout << a.h << endl;//访问A类中的私有成员
- }
- };
- };
- int A::k = 1;
- int main()
- {
- A::B b;//定义内部类对象b
- return 0;
- }
概念:在类中,我们可以对类进行不取名字的方式定义,这种定义对象的方式叫做匿名对象。其中,匿名对象的生命周期只有这一行,当这一行结束时将会自动调用析构函数。
匿名对象在只需使用一次的场合下非常好用,这里我们先了解一下,后面的文章将会深入展开这一方面。使用如下:
- #include
- using namespace std;
- class A
- {
- public:
- A(int a = 0)
- :_a(a)
- { }
- ~A()
- {
- cout << "~A()" << endl;
- }
- int GetData() {
- return _a;
- }
- private:
- int _a;
- };;
- int main()
- {
- A();//建立匿名对象,这一行结束后将调用析构函数
- A().GetData();//使用匿名对象调用成员函数,结束后将自动调用析构函数
- return 0;
- }
总:本章节只是初步学习类与对象中的知识点,具体的深入探索到了后面会详细讲解,这里我们只需先理解并会用即可。