文章目录
- 前言
- 再谈构造函数
- 初始化列表
- 初始化列表注意点
- explicit关键字
- 静态成员为所有类对象所共享,不属于某个具体的实例
- 友元函数
前言
本篇文章将会主要讲解构造函数的初始化列表,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对象后,会调用一次构造函数,也就打印了第一句
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
③当类中含有以下三种数据成员时候,对他们初始化必须使用初始化列表,分别是:
- 引用成员变量
- const成员变量
- 自定义类型成员变量(没有默认的构造函数时候)
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};
explicit关键字
他的作用说简单点就是不允许定义对象时候使用
=形式初始化,比如:
class Date {public:explicit Date(int n){_n = n;}private:int _n;};int main(){Date date1(100);Date date2 = 100; //正常情况下,这样也是可以初始化的,但是加了explict就不可以.return 0;}
static成员
概念:主要有两种,一种是修饰数据成员,称为静态成员变量.一种是修饰成员函数,称为静态成员函数.其中静态成员变量必须在类外进行初始化,并且静态成员大小不计算在类内.
其特性为:
静态成员为所有类对象所共享,不属于某个具体的实例
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;}
静态成员函数没有隐藏的this指针,不能访问任何非静态成员
我们仍然以上面Date类为例,比如下面的修改cntans函数的错误形式:
static void cntans(){cout<<_a<<endl; //_a是静态成员,类内访问没问题.cout<<_b<<endl; //但是_b是非静态成员,由于没有this指针,所以访问_b非法.}
静态成员和静态函数也有public、protected、private3种访问级别,也可以具有返回值
这个博主就不再赘述,大家自行检查.
友元
友元和静态很相似,具有两种.修饰函数的叫做友元函数,修饰类的叫做友元类.在我们介绍这个之前,先做一个小测验吧,我们利用之前学过的重载运算符,对
<<
或者
>>
进行重载,实现的类仍然是我们上面的日期类
友元函数
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的任何成员和函数.使用方法和友元函数一模一样,不再介绍.
内部类
概念:如果一个类定义在另一个类的内部,这个内部类就叫做内部类。注意此时这个
内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去调用内部类
。外部类对内部类没有任何优越的访问权限。
注意:内部类就是外部类的友元类。注意友元类的定义,
内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元
.
特性:
- 内部类可以定义在外部类的public、protected、private都是可以的。
- 注意内部类可以直接访问外部类中的static、枚举成员,不需要外部类的对象/类名。
- sizeof(外部类)=外部类,和内部类没有任何关系
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;}