在学习了“上”篇和“中”篇后,我们对类和对象以及一些析构函数有了一定的理解,本文我们将继续深入讲解有关的其他内容。
一、初始化列表的引入
我们以之前的队列为例子(创建两个队列一个用于入栈一个用于出栈)
这个myqueue对内置类型不做处理,对自定义类型会调用其相应的构造函数,这是我们根据之前的知识得来的结论,但现在如果我们的栈不提供默认构造呢?(声明一下:在栈中,我们之前写的显示构造函数是全缺省的开辟空间的函数)此时,myqueue也无法生成其对应的构造,为了使程序正常运行,办法就是在myqueue下的类显示地去写构造,也就引入了初始化列表。
初始化列表的格式:冒号开始,逗号分割,写在构造函数的下方,如图所示
这里作者的理解是,因为stack的类已经没有默认构造函数了,它的同名函数可能是一个需要传参才能进行初始化的函数。此时需要我们写一个queue的构造函数,通过走这个函数来进行对stack类的初始化。注意,下面的{}不可省略。这样,内置类型_size就初始化为0,而作为自定义类型pushst和popst此时就可以调用他们自己创建的类之下的构造函数了(因为已经传参n了),当然,这个queue处写缺省值也是可以的。
初始化列表和函数体内初始化也可以混用,比如上面也可以这么写
初始化列表的本质可以理解为每个对象中成员定义的地方。(这里再强调一下,在queue这个类中,private中的成员只是声明,并没有开空间,而在初始化列表的地方就是将声明的成员进行定义,也就是开空间)
然而,有些对象必须在初始化列表进行初始化,在函数体内初始化就会报错。
比如1.const成员对象
这是因为,const变量必须在定义时被初始化,而我们刚才说初始化列表是成员定义的地方,所以要在上方初始化而不是函数体内部。
2.引用的对象
为了便于引用的初始化,我们增加一个形参。
3.没有默认构造自定义类型成员(必须显示传参调构造)
——————————————————————————
初始化列表,不管我们写不写,每个对象都会走一遍,对于自定义类型就会去调用默认构造
即使我的初始化列表出什么也没写他也会将每个对象走一遍(此时stack类已经有默认构造函数才没有报错)
我们来看下面的一端代码
首先A类进行初始化,走初始化列表,由于main函数中传了参数所以_a1被初始化为1,然后_a1又作为参数进行_a2的初始化,按道理来讲运行结果应该是1 1,但结果确实1和随机值,为什么呢?关于初始化列表最后一个特点:成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关。也就是说,在这个程序中,因为a2先声明,我们在走初始化列表的时候先走a2的初始化,由于此时a1是随机值所以a2的初始化为随机值,然后a1被初始化为1。至此,初始化列表的相关知识基本就这么多。接下来我们来看一个有意思的东西。
第一行是进行初始化,第二行是我们之前讲过的拷贝构造,可是第三行呢?为什么它不会报错。其实背后的猫腻是——隐式类型转换。也就是说,1并没有直接赋值给aa3,而是先构造一个A的临时对象,在用这个临时对象拷贝构造aa3,在这里进行了优化,也就是连续构造加拷贝构造优化为了直接构造。这个与我们之前学过的内置类型就对得上了。那再看看下面这个
56行的引用赋值为什么就会报错呢?首先这里也肯定有构造,也就是2创建一个A类型的临时对象,但不同的是这里面就没有拷贝构造了。也就是说aa4引用的是类型转换中用2构造的临时对象。但报错的原因其实是权限的放大,所以我们在前面加const即可。
这个东西到底有啥用呢?我们引入一个实例,栈的压栈操作。
这是正常写法,但不足的是,它首先要构造,然后作为参数传参又要拷贝构造造成浪费。此时,我们上面的操作就起作用了。
209行代码就可以平替207+208行,这样就直接实现相同的压栈操作了(2的隐式类型转换给了push函数,且注意要加const否则会报错,因为会造成权限的放大)那多参数的怎么搞呢?这里是这样规定的
同理,237+238行与240的压栈操作结果相同。
二、explicit关键字
如果我们不想进行隐式类型转换,把构造函数用explicit关键字修饰即可。
三、静态成员变量与静态成员函数
声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量
如果我们去计算这个类的大小会发现是8而不是12,因为静态成员变量在静态区存储,类中不存储成员函数,不存储静态成员变量。此时我们不能在此处给缺省值,因为这个类走初始化列表的时候是不对静态成员变量进行初始化的。应该在全局进行定义。我们想访问的话,用以下两种办法均可
注意,此时是对静态成员变量进行了public处理,如果放在private中就会无法访问,但也有静态成员变量即是private同时我们也能访问的办法,用静态成员函数。
用static修饰的 成员函数,称之为静态成员函数。
注意,在静态成员函数中,我们无法访问类中其他对象的成员,只能访问静态成员,因为在静态成员函数中是没有this指针的。
这样就可以解决问题了。我们再总结一下静态成员变量和函数的一些特性:1. 静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区 2. 静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明 3. 类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问 4. 静态成员函数没有隐藏的this指针,不能访问任何非静态成员 5. 静态成员也是类的成员,受public、protected、private 访问限定符的限制
四、友元
1.我们在(中)篇已经讲解了它的语法和用途,在此就简单再回顾一下即可,我们在实现日期类的时候,为了输出类中的内容,实现<<和>>运算符重载函数,发现为了符合我们所认知的cout和cin用法,这两个函数只能写在类域的外部,这就造成了无法访问类中成员的问题,为了解决我们引入了友元。
2.友元类
我们有时候创建两个类的时候,会想在其中一个类中去访问另一个类的成员,显然这是不可以的,但我们把它写成友元类即可,它的写法和友元函数相同 friend class 类名,可以写在想访问的类的任意位置。
这个地方声明的位置不要颠倒,这里是date类可以访问time类的成员,而time类仍无法访问date类 的成员。
五、内部类
如果一个类定义在另一个类的内部,这个内部类就叫做内部类。内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越的访问权限。它仅仅是受到外部类的类域限制。
看,我们在定义B类的变量时不可以直接去像A类那样写,就是因为类域的限制。但如果计算A类的大小,B类中的成员变量并不算入其中,这也进一步说明内部类和外部类的独立性。
但是B天生就是A的友元,这也是内部类的一个特点。内部类随便访问外部类,但外部类不可访问内部类。
六、匿名对象(了解即可)
假设我们随便写一个类,然后创建一个变量,但有时候我们创建的变量没有名字,就叫匿名对象,我们来感受一下他们的差别
35行就是匿名,37行就是我们平常所定义的变量。那么他们在功能上有什么差别呢?
匿名对象的生命周期只在当前这一行,也就是说代码走到35行时开始初始化,构造,一旦走到36行它就调析构函数。而且,我们用匿名对象去调用成员函数也可以在一行实现
————
此系列到此就完结撒花了,希望朋友们多多点赞,后续继续更新高质量文章。