本篇文章将会主要讲解构造函数的初始化列表,static成员,以及内部类,目的是对前几章讲解类时候的一个深入和总结.
我们上一节讲解构造函数时候,了解了构造函数的定义和使用,明白编译器会通过调用构造函数给类成员一个初始值,但是注意了~~,这个过程却并不是初始化,而是只能叫做赋初值,因为初始化只能一次,比如下面的一段代码:
class A {
public:
A(int c = 10) {
cout << "A(int c)" << endl;
_c = c;
}
private:
int _c;
};
class B {
public:
B(int a,int b,A& ca) {
_a = a;
_b = b;
_ca = ca;
}
private:
int _a;
int _b;
A _ca;
};
int main() {
A a(20);
B b(1,2,a);
return 0;
}
大家觉得上面的结果会是什么?没错,会打印两句A(int c)
,但是博主在这里有个小小问题,就这两句分别在哪一行代码后打印的呢?
答案:
A(int c)
.B(int a,int b,A& ca)
之后,程序_a = a
之前,也就是在这两句之间,不信吗?我们下图为例:当程序执行到_a = a
以后,已经打印出了A(int c)
,那么这是为什么呢? 请看下一小节的初始化列表.
以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式
什么意思呢?如下:
class Date {
public:
Date(int year, int month, int day)
:_year(year),_month(month),_day(day)
{
}
void Print(){
cout<<"year是"<<_year<<endl;
cout<<"month是"<<_month<<endl;
cout<<"day是"<<_day<<endl;
}
private:
int _year;
int _month;
int _day;
};
int main(){
Date date(2021,10,23);
date.Print();
return 0;
}
我们可以清晰地看到,通过构造函数的初始化列表,就已经成功初始化了类数据成员.所以现在大家应该已经明白了,为什么说初始化列表才是真正的初始化数据成员,而通过构造函数的函数体赋值形式叫做初次赋值.因为在函数体内部的赋值性质是可以修改初始化列表的结果,比如把构造函数改成下面这样:
Date(int year, int month, int day)
:_year(year),_month(month),_day(day)
{
_year = 2000;
_month = 1;
_day = 1;
}
那么刚才的结果将会是这样:
①每个数据成员只能在初始化列表中出现一次(因为只能初始化一次),还是按照上面的Date类为例,若这样写就错误了:
Date(int year, int month, int day)
:_year(year),_month(month),_day(day),_year(2000),_month(10),_day(20)
{
}
②数据初始化顺序只和数据声明顺序有关,与初始化列表无关:
class A
{
public:
A(int a)
:_a1(a)
,_a2(_a1)
{
}
void Print() {
cout<<_a1<<" "<<_a2<<endl;
}
private:
int _a2;
int _a1;
};
该类的结果为:
我们能够发现,_a2
的值并不是100,因为最开始初始化的数据就是_a2
,但是给_a2
的值缺是还没有初始化的_a1
③当类中含有以下三种数据成员时候,对他们初始化必须使用初始化列表,分别是:
class A
{
public:
A(int a)
:_a(a)
{
}
private:
int _a;
};
class B
{
public:
B(int a, int ref)
:_aobj(a)
,_ref(ref)
,_n(10)
{
}
private:
A _aobj; // 没有默认构造函数
int& _ref; // 引用
const int _n; // const
};
他的作用说简单点就是不允许定义对象时候使用
=
形式初始化,比如:
class Date {
public:
explicit Date(int n)
{
_n = n;
}
private:
int _n;
};
int main()
{
Date date1(100);
Date date2 = 100; //正常情况下,这样也是可以初始化的,但是加了explict就不可以.
return 0;
}
概念:主要有两种,一种是修饰数据成员,称为静态成员变量.一种是修饰成员函数,称为静态成员函数.其中静态成员变量必须在类外进行初始化,并且静态成员大小不计算在类内.
其特性为:
class Date {
public:
Date(int b = 10)
{
_a++;
_b = b;
}
static void cntans()
{
cout<<_a<<endl;
}
private:
static int _a;
int _b;
};
int Date::_a = 10; //静态成员必须在外面初始化.
int main()
{
Date d1;
Date d2;
Date d3;
d1.cntans();
d2.cntans();
d3.cntans();
return 0;
}
大家猜猜会打印什么呢?答案如下:
可以清晰的看到,三次打印都是13,原因就是因为静态成员由所有类对象共享,并不是属于某个具体对象
在上面我们已经使用过了对象.静态成员访问
格式,现在就介绍一下类名::静态成员
.
仍然以上面为例:
int main(){
Date d1;
Date::cntans(); //类名::静态成员格式
return 0;
}
我们仍然以上面Date类为例,比如下面的修改cntans函数的错误形式:
static void cntans()
{
cout<<_a<<endl; //_a是静态成员,类内访问没问题.
cout<<_b<<endl; //但是_b是非静态成员,由于没有this指针,所以访问_b非法.
}
这个博主就不再赘述,大家自行检查.
友元和静态很相似,具有两种.修饰函数的叫做友元函数,修饰类的叫做友元类.在我们介绍这个之前,先做一个小测验吧,我们利用之前学过的重载运算符,对<<
或者>>
进行重载,实现的类仍然是我们上面的日期类
class Date
{
public:
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{
}
ostream& operator<<(ostream& out)
{
out<<_year<<"-"<<_month<<"-"<<_day;
return cout;
}
private:
int _year;
int _month;
int _day;
};
大家觉得我们这样进行重载,会不会有什么问题呢?我们先调用一下试试吧.
Date date(2021,10,10);
cout << date;
我们进行编译,运行然后就能发现结果报错:
为什么呢?我们之前在讲运算符重载时候强调过,我们在写参数列表时,需要按照运算符的操作数格式进行重载.但是这里呢>我们按照了吗,并没有,因为这里有隐藏的this指针,也就是说操作数弄反位置了.那怎么进行修改呢?目前我们的办法只有一个,那就是放到全局:
ostream& operator<<(ostream& out,Date& date)
{
out << _year << "-" << _month << "-" << _day;
return cout;
}
但是像这样又会遇到一个问题,那就是
_year
等成员是私有的,在外部无法访问.那又怎样进行解决呢?现在就是我们的老大哥—friend
友元驾临.
这种情况我们只需要在类Date中的任何位置放一份重载函数声明,并在前面加上friend
.
class Date
{
friend ostream& operator<<(ostream& out,Date& date); //一般习惯性的是加在这里
public:
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{
}
private:
int _year;
int _month;
int _day;
};
注意:友元函数,只需要在函数的声明前加上friend
就行,并且友元不属于任何类,只是为了突破类的作用域限制,以达到访问类的私有或保护成员.
并且友元函数不可以用const
进行修饰,不受任何的类作用域符限制,而同一个友元函数还可以是多个类的友元.
友元类和友元函数相似,当一个类A是类B的友元类后,类A便可以访问B的任何成员和函数.使用方法和友元函数一模一样,不再介绍.
概念:如果一个类定义在另一个类的内部,这个内部类就叫做内部类。注意此时这个内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去调用内部类
。外部类对内部类没有任何优越的访问权限。
注意:内部类就是外部类的友元类。注意友元类的定义,内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元
.
特性:
class A
{
private:
static int k;
int h;
public:
class B
{
public:
void foo(const A& a)
{
cout << k << endl;//
cout << a.h << endl;// //想要访问外部类的其他成员,必须通过外部类的对象参数.
}
};
};
int A::k = 1;
int main()
{
A::B b; //定义内部类,只能通过作用域限制符.
b.foo(A());
return 0;
}