类和对象终
一、初始化列表
再谈构造函数
- 我们之前实现构造函数的时候,初始化成员变量在函数体内赋值的,构造函数还有一种初始化方式,就是初始化列表
我们先实现一个栈来举例:
// 实现一个栈
typedef int DataType;
class Stack {
public:// 默认构造Stack(size_t capacity = 4) {_arr = (DataType*)malloc(sizeof(DataType) * capacity);if (_arr == nullptr) {perror("_arr malloc err!");return;}_capacity = capacity;_top = 0;}void Push(DataType val) {_arr[_top++] = val;}// 其他方法实现~Stack() {if (_arr) {free(_arr);_arr = nullptr;_top = _capacity = 0;}}
private:DataType* _arr;int _top;size_t _capacity;
};
// 两个栈实现一个队列
class MyQueue {
private:Stack _pushst;Stack _popst;int _top;
};
int main() {MyQueue q;return 0;
}
我们不写MyQueue
的构造函数,编译器会默认生成MyQueue
的默认构造函数,对于自定义类型,会去调用它的默认构造函数,而对于内置类型,编译器处理or不处理是未知的,取决于编译器
但是如果Stack
没有默认构造的话,MyQueue
里的自定义类型去调用Stack
中的默认构造就会失败
- 初始化列表的使用方式是以一个冒号开始,逗号分隔的数据或成员列表,每个成员变量后面跟一个括号,括号里面是初始化的值或者表达式
- 每个成员变量在初始化列表中只能出现一次,本质可以理解为每个对象中的成员定义的地方
const
成员变量、引用成员变量、没有默认构造的类类型变量(必须显式传参调用构造),必须放在初始化列表初始化,否则会报错
typedef int DataType;
class Stack {
public:// 默认构造Stack(size_t capacity = 4) {_arr = (DataType*)malloc(sizeof(DataType) * capacity);if (_arr == nullptr) {perror("_arr malloc err!");return;}_capacity = capacity;_top = 0;}void Push(DataType val) {_arr[_top++] = val;}// 其他方法实现~Stack() {if (_arr) {free(_arr);_arr = nullptr;_top = _capacity = 0;}}
private:DataType* _arr;int _top;size_t _capacity;
};
// 两个栈实现一个队列
class MyQueue {
public:// 若Stack不具备默认构造,那么MyQueue也无法生成默认构造// 1、引用 2、const 3、没有默认构造自定义类型成员(必须显示传参调构造)MyQueue(int n, int& rr):_pushst(n), _popst(n), _top(0),_val(1),_ref(rr){}
private:Stack _pushst;Stack _popst;int _top;// 必须在定义时初始化// C++11给缺省值const int _val = 0;int& _ref;
};
int main() {int aa = 0;MyQueue q1(10,aa);return 0;
}
const
修饰的变量和引用变量,它只有一次初始化的机会,就是在定义的时候,所以我们说初始化列表本质就是每个对象中成员定义的地方
5. C++11对缺省值做了补充,给在private
中声明的成员变量赋值本质是给缺省值
- 以后尽量使用初始化列表初始化,因为那些不在初始化列表初始化的成员也会先走一边初始化列表,如果这个成员在声明位置给了缺省值,初始化列表就会使用这个缺省值初始化。如果没有给缺省值,对于没有显式在初始化列表初始化的内置类型成员是否会初始化取决于编译器;而没有显式在初始化列表的自定义类型成员,会调用自己的默认构造函数,无默认构造函数,则会报错
- 成员变量在类中声明次序就是在初始化列表中初始化顺序
#include<iostream>
using namespace std;
class A
{
public:A(int a):_a1(a), _a2(_a1) {}void Print() {cout << _a1 << " " << _a2 << endl;}
private:int _a2 = 2;int _a1 = 2;
};
int main()
{A aa(1);aa.Print();
}
这段代码的输出结果是什么?打印1 随机值
分析:
- 声明顺序:
_a2
_a1
- 初始化顺序:
_a2
_a1
所以,_a1
赋值给_a2
,_a2
是随机值,虽然给了缺省值,_a1
的缺省值为2,但是这个缺省值只能在初始化_a1
的时候使用,而_a2
并没有接收自己的缺省值,因为手动给了_a1
,此时_a1
是随机值,然后a
赋值给_a1
_a1 = 1
所以最后打印:1 随机值
总结
- 无论是否显示写初始化列表,每个构造函数都有初始化列表;
- 无论是否在初始化列表显示初始化成员变量,每个成员变量都要走初始化列表初始化
二、 类型转换
- CPP支持内置类型隐式转换为类类型对象,但需要相关内置类型为参数的构造函数
- 构造函数前⾯加
explicit
就不再⽀持隐式类型转换 - 类类型对象之间也可以隐式转换,但需要相关构造函数
// 类型转换
class classA {
public:// 单参数构造classA(int a):_aa(a){cout << "classA(int a)\n";}// 多参数构造classA(int a1, int a2):_a1(a1), _a2(a2){cout << "classA(int a1, int a2)\n";}
private:// 声明给缺省值int _aa = 0;int _a1 = 1;int _a2 = 3;
};int main() {classA a1(1);// 拷贝构造classA a2 = a1;// 隐式类型转换// 3构造了一个classA的临时对象,再用这个临时对象拷贝构造a3// 这里经历了构造、拷贝两个过程,编译器优化为一步,直接构造classA a3 = 3;// raa 引用的是类型转换中用3构造的临时对象 const classA& raa = 3;// 多参数传参用大括号classA aa1 = { 1 , 2 };const classA& raaa = { 1 , 3 };return 0;
}
三、static成员
- 静态成员变量要在类外进行初始化
- 静态成员变量被所有类对象共享,不属于某个对象,存放在静态区
- 静态成员函数中可以访问其他的静态成员,但是不能访问⾮静态的,因为没有
this
指针 - 非静态的成员函数,可以任意访问静态的成员变量和静态的成员函数
- 可以通过
::静态成员
或者对象.静态成员
来访问静态成员和静态成员函数 - 静态成员也是类的成员,受
public
、protected
、private
访问限定符限制 - 静态成员变量不能在声明位置给缺省值初始化,因为缺省值是构造函数初始化列表的,静态成员变量不属于任何对象,不走初始化列表
// 实现一个类 计算创建出了多少类对象
// 有对象的创建就++ 有析构就-- ,用静态变量存储这个值,最后返回最终值
class classB {
public:classB() {++_SumCnt;}classB(const classB& t) {++_SumCnt;}~classB() {--_SumCnt;}static int GetBCount() {return _SumCnt;}// 类里面声明// 不能给缺省值,因为缺省值是给初始化列表// 在静态区而不在对象中,不走初始化列表static int _SumCnt;
private:int _b1;int _b2;
};
classB func() {classB bb4;return bb4;
}// 类外面初始化 即定义
int classB::_SumCnt = 0;int main() {classB bb1;cout << sizeof(bb1) << endl;classB bb2;classB bb3(bb2);func();// 直接访问cout << classB::GetBCount() << endl;}
注意看sizeof(bb1)
的大小,private
中有两个int
类型成员变量,public
中有一个int
类型静态成员变量
到底是12还是8呢?答案是8,因为静态成员变量存在静态区,不属于任何一个对象
对于创建了几个对象的问题bb1
bb2
bb3
bb4
在一个栈区的函数中创建,出函数作用域生命周期就结束了,所以总共有三个对象被创建
四、友元函数
- 友元提供了一种突破类访问限定符封装的方式,分为友元类和友元函数,在函数声明的前面加
friend
,并将友元的声明放到一个类的里面 - 外部友元函数可以访问私有保护成员,友元函数仅仅是一种声明,声明我是这个类的朋友,我有权限访问这个类的数据,但是它不是类的成员
- 一个函数可以是多个类的友元函数
- 友元类的成员函数可以是另一个类的友元函数,都能访问另一个类中的私有和保护成员
- 友元类的关系是单向的,不能传递的,A是B的友元,但B不是A的友元,A是B的友元,B是C的友元,但A不是C的友元
class Time {// 声明Date是Time的友元,但是Time不是Date的友元// Date类中可以访问Time类中的成员,但是Time类中不能访问Date中的成员friend class Date;
public:Time(int hour = 10, int minute = 12, int second = 24):_hour(hour), _minute(minute), _second(second){}
private:int _hour;int _minute;int _second;
};
class Date {
public:Date(int year = 2025, int month = 4, int day = 16):_year(year), _month(month), _day(day) {// 直接在Date类中访问Time类的_hour_t._hour = 0;}void SetTimeOfDate(int hour, int minute, int second) {// 直接访问Time类的成员_t._hour = hour;_t._minute = minute;_t._second = second;}
private:int _year;int _month;int _day;// 在Date类中声明Time类Time _t;
};
五、内部类
- 一个类定义在另一个类的内部,这个内部的类就称为内部类。内部类是独立的类,仅仅受到类域和访问限定符的限制,所以外部类定义的对象中不包含内部类
- 内部类是天生的外部类的友元,eg:A类和B类,把A类放入B类,那么A天生是B的友元
- 内部类本质也是一种封装,如果A和B关系紧密,A实现出来就是给B使用的,而不想让其他类使用A,那么可以考虑把A设计为B的内部类,若放到
private
或者protected
中,那么A就是B的专属内部类,其他类使用不了
class B {
public:// A是B的内部类// 仅仅是将A放到B中,还受到类域和访问限定符的限制class A { // A天生是B的友元private:int a1;};
private:int b1;char b2;
};