1 构造函数的初始化列表
1️⃣ 在前面的学习中,我们知道在类中,成员变量只是给出了一个声明,并没有定义!也就是说并没有开辟空间!当我们创建对象,会对这个对象整体进行定义!对于内置类型就会不做处理给一个随机值!对于自定义类型就会去调用它的构造函数!其实对于成员变量的定义是利用初始化列表来完成的
初始化列表的格式:以冒号开始,接着是一个以逗号分隔的数据成员列表,每个成员变量后面跟着一个放在括号里面的初始值或者是表达式
class Date { //声明 private: int _year; int _month; int _day; public: Date(int year, int month, int day) //初始化列表 :_year(year), _month(month), _day(day) {} };
在C++中有些成员变量是必须利用初始化列表进行显示初始化的,例如引用成员变量必须要求我们在定义的时候进行赋值,以及const成员变量也必须要在定义的初始化,可以参考以下代码:
我们可以发现在函数体内部是不可以对引用初始化的,也是不可以初始化常量n的,这是因为函数体内部是进行赋值操作的,是给定一个初值的,并且在函数体内部是可以进行多次赋值的!而初始化只能初始化一次(可以理解为通过初始化列表显示定义只可以定义一次,同时给定一个初始值)。
对于之前我们可以理解为调用构造时,也是走的初始化列表进行定义,只不过当时啥也没有写,同时这就和之前C++11打的补丁联系上了,可以给成员变量一个缺省值,在走初始化列表时,由于没有赋初始值,所以默认就会以缺省值来定义,而如果我们没有赋初始值或给定一个缺省值,对于内置类型就是随机值,自定义类型就会去调用它的构造函数,利用初始化列表就可以很好的解决上诉引用初始化,常量初始化的问题:
2️⃣ 在学习构造函数时,我们知道对于内置类型是给定随机值,对于自定义类型是会去调用它的默认构造的,但是如果自定义类型没有默认构造是会报错的!代码如下:
class people { private: int _age; public: people(int age) { _age = age; } }; class Date { //声明 private: int _year; int _month; int _day; int& tmp; const int n; people p; public: Date(int year, int month, int day) //初始化列表 :_year(year), _month(month), _day(day), tmp(year), n(1) {} }; int main() { Date d1(2023, 11, 11);//对象整体进行定义,开辟空间 return 0; }
运行结果:
C++为了解决自定义类型有时没有默认构造函数这一缺点,也是利用初始化列表进行解决,通过初始化列表可以调用其他的构造函数了:只需要在上诉代码进行一点改进即可:
下面我们再来用一个例子来感受一下初始化列表的作用,以两个栈实现一个队列为例:
class Stack { private: int top; int capacity; int* _a; public: Stack(int capacity) { //初始化栈的操作 } }; class myqueue { private: int _size = 0; Stack _push; Stack _back; public: myqueue() {} };
我们知道其实通过myqueue构造函数,对于自定义类型,是会去先调用它的默认构造函数的,但是这里如果没有默认的构造函数怎么办?可以使用初始化列表:
//通过初始化列表来调用自定义类型的构造 myqueue() :_push(10), _back(20) {} //还可以根据自己的需求传入初始化长度结合初始化列表来构造出我们需要的栈的大小 myqueue(int n1, int n2) :_push(n1), _back(n2) {}
3️⃣类的成员变量声明的顺序就是在初始化列表中的初始化顺序,与其在初始化列表的顺序无关
class A { private: int _a2; int _a1; public: A(int x) :_a1(x), _a2(_a1) {} void print() { cout << _a1 << " " << _a2 << endl; } }; int main() { A a(1); a.print(); return 0; }
你认为打印出的_a1和_a2分别是多少呢?其实根据声明的顺序来的,先初始化_a2,在初始化_a1的!而这里_a1没有初始化是一个随机值,所以_a2是随机值,_a1就是1
2 static成员
静态成员变量:
1️⃣在类中如果有成员变量被static修饰,被称为静态成员变量。
2️⃣并且静态成员变量的定义(初始化)必须在类外,定义时不添加static关键字,类中只是声明!
3️⃣另外还需要注意的就是静态成员变量不是某一个对象所拥有,是所有对象共享,是存放在静态区。
4️⃣在计算类的大小是不包括静态成员的。
静态成员函数:类中的成员函数被static关键字所修饰,静态成员函数没有隐藏的this指针!
了解了上诉内容,如何来计算程序中创建出多少个类对象?
//计算机创建了多少个对象 class A { private: static int _scout;// public: A() { ++_scout; } A(const A& tmp) { ++_scout; } static int get_scout() { return _scout; } }; A func(A tmp) { A a1(tmp); return a1; } int A::_scout = 0; int main() { A aa; func(aa); cout << A().get_scout() << endl;//使用匿名对象调用函数,匿名对象的生命周期只是在这一行 //还可以使用域作用符来访问,如果是非静态成员函数是不可以用这种的 cout << A::get_scout << endl; return 0; }
分析如下:
所有总共有4个对象,可能在编译器跑起来不会是4个,那是因为编译器在这里会做优化处理!后面我们就会了解到编译器优化是遵循怎么样的原则!
对于静态函数,我们或许看到过这样的问题:
1.静态成员函数可以调用非静态成员函数吗?
答:不可以,因为静态成员函数中没有this指针,所以不可以调用非静态成员函数!
2.非静态成员函数可以调用类的静态成员函数吗?
答:可以,非静态成员函数中有隐藏的this指针,通过隐藏的this指针就可以队静态成员函数进行调用了。
3 explicit关键字
构造函数具有初始化对象的作用,但是对于单个参数或者除第一个参数无默认值其余均有默认值的构造函数
,还具有类型转换的作用。想要了解这个关键字的作用,我们先来看看这样一段代码:
class people { private: int _age; public: people(double age) { _age = age; } }; int main() { people p1(1); //调用拷贝构造?这里为什么整型可以直接定义对象了? people p2 = 10; }
这是因为发生了构造函数的隐式类型转换
而explicit关键字就是用来防止这种隐式的类型转换的!加上就转换不了了:
但是如果使用强制类型转换还是可以的!
4 友元
友元提供了一种突破封装的方式,可以让我们访问到私有的成员变量,有时提供了便利。但友元会增加代码的耦合度,所以不宜多用!
4.1 友元函数
用
friend关键字
在类中进行声明,在类外进行定义!这里需要注意的是友元函数并不是类的成员函数!
4.1.1 流插入运算符的重载
我们可以发现我们不能打印我们自定义类型的值!而可以打印内置类型的值!这是因为C++库中帮我们实现了对内置类型函数的流插入运算符的重载:
我们也可以通过重载流插入操作符来实现输入自定义类型,代码如下:
class Date { private: int _year; int _month; int _day; public: Date(int year = 2023, int month = 11, int day = 22) { _year = year; _month = month; _day = day; } void print() { cout << _year << " " << _month << " " << _day<<endl; } ostream& operator<<(ostream& _cout) { _cout << _year << " " << _month << " " << _day; } };
我们可以发现cout<<d1会编译不通过,这是因为成员函数中的第一个参数就是隐藏的this指针!所以就会导致参数类型不匹配!那么应该如何正确的写出我们平常所见的输出呢?可以利用友元函数!
class Date { private: int _year; int _month; int _day; public: Date(int year = 2023, int month = 11, int day = 22) { _year = year; _month = month; _day = day; } void print() { cout << _year << " " << _month << " " << _day<<endl; } friend ostream& operator<<(ostream& _cout, Date& d); }; ostream& operator<<(ostream& _cout, Date& d) { _cout << d._year << " " << d._month << " " << d._day << endl; return _cout; } int main() { Date d1; cout << d1; return 0; }
4.1.2 流提取运算符重载
同理我们也可以写出流提取运算符的重载
class Date { private: int _year; int _month; int _day; public: Date(int year = 2023, int month = 11, int day = 22) { _year = year; _month = month; _day = day; } void print() { cout << _year << " " << _month << " " << _day<<endl; } friend ostream& operator<<(ostream& _cout, Date& d); friend istream& operator>>(istream& _cin, Date& d); }; //重载实现流插入 ostream& operator<<(ostream& _cout, Date& d) { _cout << d._year << " " << d._month << " " << d._day << endl; return _cout; } //重载实现流提取 istream& operator>>(istream& _cin, Date& d) { _cin >> d._year; _cin >> d._month; _cin >> d._day; return _cin; } int main() { Date d1; cout << d1; cin >> d1; cout << d1; return 0; }
4.2 友元类
就是在一个类中声明,哪一个类是这个类的友元类,例子如下:
class Time { friend class Date;//声明日期类就是时间类的友元类!在日期类中就可以访问到时间类的私有成员 private: int _hour; int _minute; int _second; public: Time(int hour = 0,int minute = 0,int second = 0) :_hour(hour), _minute(minute), _second(second) {} }; class Date { private: int _year; int _month; int _day; Time _t; public: void SetTimeofDate(int hour, int minute, int second) { _t._hour = hour; _t._minute = minute; _t._second = second; } };
1️⃣ 友元关系是单向的。Date类是Time类的友元类,所以Date类中是可以访问到Time类中的私有变量,而Time类是访问不到Date类中的私有变量
2️⃣友元关系是不可以传递的,比如A是B的友元,B是C的友元,但是不可以推出A是C的友元!
3️⃣友元是不可以继承的
5 内部类
简单来说就是一个类里面又包含了一个类:
class A { /// class B { // }; };
内部类也是一个独立的类,只不过它被限定在外部类的作用域里面了!外部类对内部类的访问权限是没有任何优越的!
class A { private: int _a1; static int k; class B { private: int _b1; public: void test(const A& tmp) { cout << k << endl; cout << tmp._a1 << endl; } }; }; int A::k = 100; int main() { A::B b1; cout << sizeof(A) << endl; return 0; }
以上代码我们可以了解到内部类有关特性:
1️⃣计算外部类的大小是不包括内部类的
2️⃣内部类是可以直接访问外部类的静态成员,也可以通过外部类对象访问外部类的成员变量,但是外部类是不可以访问内部类的成员的
3️⃣内部类是外部类的友元类,外部类不是内部类的友元类