2.c++面向对象(五)
一.再谈构造函数
1.初始化列表
#include<iostream>
using namespace std;
#include<vector>
#include<list>typedef int DataType;
class Stack
{
public:Stack(size_t capacity = 4){_array = (DataType*)malloc(sizeof(DataType) * capacity);if (NULL == _array){perror("malloc申请空间失败!!!");return;}_capacity = capacity;_size = 0;}void Push(DataType data){// CheckCapacity();_array[_size] = data;_size++;}// 其他方法...~Stack(){if (_array){free(_array);_array = NULL;_capacity = 0;_size = 0;}}private:DataType* _array;int _capacity;int _size;
};class MyQueue
{
public:// Stack不具备默认构造。MyQueue也无法生成默认构造// 初始化列表// 初始化列表本质可以理解为每个对象中成员定义的地方// 所有的成员,你可以在初始化列表初始化,也可以在函数体内初始化// 1、引用 2、const 3、没有默认构造自定义类型成员(必须显示传参调构造)MyQueue(int n, int& rr):_pushst(n),_popst(n),_x(1),_ref(rr){_size = 0;//_x = 1;}private:// 声明Stack _pushst;Stack _popst;int _size;// 必须在定义时初始化const int _x;// 必须在定义时初始化int& _ref;
};int main()
{int xx = 0;MyQueue q1(10, xx);//MyQueue q2;const int y = 1;int& ry = xx;return 0;
}
能写初始化列表,就写初始化列表
const 变量必须在定义的时候进行初始化,初始化之后就不能改变了
引用 (&) 也是一样的,要在定义的时候进行初始化
1、引用 2、const 3、没有默认构造自定义类型成员(必须显示传参调构造)
2.尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量, 一定会先使用初始化列表初始化。
总结: 实践中,尽可能使用初始化列表进行初始化,不方便的再使用函数体进行初始化
3. 成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后 次序无关
我们可以看一下下面这段代码:
class A
{
public:A(int a): _a1(a), _a2(_a1){}void Print() {cout << _a1 << " " << _a2 << endl;}private:int _a2;int _a1;
};int main()
{A aa(1);aa.Print();return 0; // 通常建议添加main函数的返回值
}
可以看一下这个地方的答案:
选择D选项 (成员变量在类中进行声明的顺序是在类中进行定义的顺序)
二.隐式类型转换
1.隐式类型转换的使用
class A
{
public:// 单参数构造函数//explicit A(int a)A(int a):_a(a){cout << "A(int a)" << endl;}// 多参数构造函数A(int a1, int a2):_a(0),_a1(a1),_a2(a2){}A(const A& aa):_a(aa._a){cout << "A(const A& aa)" << endl;}private:int _a;int _a1;int _a2;
};int main()
{A aa1(1);// 拷贝构造A aa2 = aa1;// 隐式类型转换// 内置类型转换为自定义类型// 3构造一个A的临时对象,在用这个临时对象拷贝构造aa3// 编译器遇到连续构造+拷贝构造->优化为直接构造A aa3 = 3;// raa 引用的是类型转换中用3构造的临时对象 const A& raa = 3;A aaa1(1, 2);A aaa2 = { 1, 2 };const A& aaa3 = { 1, 2 };return 0;
}
这里编译器做了优化将连续的构造和拷贝构造,转化成为直接构造
raa 引用的是类型转换中用3构造的临时对象
上述隐式类型感觉没啥用处,下面我们来举一个列子,来进行观测一下
class A
{
public:// 单参数构造函数//explicit A(int a)A(int a):_a(a){cout << "A(int a)" << endl;}// 多参数构造函数A(int a1, int a2):_a(0),_a1(a1),_a2(a2){}A(const A& aa):_a(aa._a){cout << "A(const A& aa)" << endl;}private:int _a;int _a1;int _a2;
};class Stack
{
public:void Push(const A& aa){//...}//...
};int main()
{Stack st;A a1(1);st.Push(a1);A a2(2);st.Push(a2);st.Push(2);st.Push(4);A aa1(1, 2);st.Push(aa1);st.Push({ 1,2 });
}
1.单参数的构造函数
2.多参数的隐式类型转换
C++11 中,我们一般使用的是{}进行初始化
我们的缺省值,除了可以上面那样给,还可以向下面这样给:
class A
{
public:// 单参数构造函数//explicit A(int a)A(int a):_a(a){cout << "A(int a)" << endl;}// 多参数构造函数A(int a1, int a2):_a(0),_a1(a1),_a2(a2){}A(const A& aa):_a(aa._a){cout << "A(const A& aa)" << endl;}private:int _a;int _a1;int _a2;
};class BB
{
public:BB(){}private:// 声明给缺省值int _b1 = 1;int* _ptr = (int*)malloc(40);Stack _pushst = 10;A _a1 = 1;A _a2 = { 1,2 };A _a3 = _a2;
};int main()
{BB bb;return 0;
}
2.explicit关键字
构造函数不仅可以构造与初始化对象,对于单个参数或者除第一个参数无默认值其余均有默认值 的构造函数,还具有类型转换的作用。
class Date
{
public:// 1. 单参构造函数,没有使用explicit修饰,具有类型转换作用// explicit修饰构造函数,禁止类型转换---explicit去掉之后,代码可以通过编译explicit Date(int year): _year(year){}/*// 2. 虽然有多个参数,但是创建对象时后两个参数可以不传递,没有使用explicit修饰,具有类型转换作用// explicit修饰构造函数,禁止类型转换explicit Date(int year, int month = 1, int day = 1): _year(year), _month(month), _day(day){}*/Date& operator=(const Date& d){if (this != &d){_year = d._year;_month = d._month;_day = d._day;}return *this;}private:int _year;int _month;int _day;
};void Test()
{Date d1(2022);// 用一个整形变量给日期类型对象赋值// 实际编译器背后会用2023构造一个无名对象,最后用无名对象给d1对象进行赋值d1 = 2023;// 将1屏蔽掉,2放开时则编译失败,因为explicit修饰构造函数,禁止了单参构造函数类型转换的作用
}
三.static成员
1.静态成员变量
static变量是存储在静态区中的,static 对象是不能给缺省值(缺省值 是给初始化列表的,初始化列表是给对象成员的)
只有声明没有定义是会报错的
我们可以统计有多少个对象还在使用:
class A
{
public:A() { ++_scount;}A(const A & t) { //GetCount();++_scount;}~A(){ //--_scount;}// 没有this指针,只能访问静态成员static int GetCount(){//_a1 = 1;return _scount;}
private:// 声明int _a1 = 1;int _a2 = 1;
// public:// 声明// 静态区,不存在对象中// 不能给缺省值,因为缺省值是给初始化列表// 他在静态区不在对象中,不走初始化列表// 属于所有整个类,属于所有对象static int _scount;
};// 定义
int A::_scount = 0;A func()
{A aa4;return aa4;
}int main()
{A aa1;cout << sizeof(aa1) << endl;A aa2;A aa3(aa1);func();//aa1._scount++;//cout << A::_scount << endl;cout << A::GetCount() << endl;return 0;
}
2.静态成员函数
用static修饰成员函数的时候,没有this指针,我们没有创建对象就能直接使用
我们可以通过公有的成员函数进行调用
3.特性
1. 静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区
2. 静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明
3. 类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问
4. 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
5. 静态成员也是类的成员,受public、protected、private 访问限定符的限制
4.题目
1.求 1+2+3+...+n
https://www.nowcoder.com/practice/7a0da8fc483247ff8800059e12d7caf1?tpId=13&tqId=11200&tPage=3&rp=3&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking
#include <regex>
class sum
{
public:sum(){_sum += _i;++_i;}static int get_ret(){return _sum;}
private:static int _i;static int _sum;
};int sum::_sum = 0;
int sum::_i = 1;class Solution {
public:int Sum_Solution(int n) {sum arr[n];return sum::get_ret();}
};
四.友元
友元提供了一种突破封装的方式,有时提供了便利。
但是友元会增加耦合度,破坏了封装,所以 友元不宜多用。 友元分为:友元函数和友元类
1.友元函数
#include <iostream>
using namespace std;class Date
{// 友元声明,允许外部函数访问私有成员friend ostream& operator<<(ostream& _cout, const Date& d);friend istream& operator>>(istream& _cin, Date& d);public:// 带默认参数的构造函数Date(int year = 1900, int month = 1, int day = 1): _year(year), _month(month), _day(day){}private:int _year; // 年int _month; // 月int _day; // 日
};// 重载输出运算符,用于打印Date对象
ostream& operator<<(ostream& _cout, const Date& d)
{_cout << d._year << "-" << d._month << "-" << d._day;return _cout;
}// 重载输入运算符,用于输入Date对象
istream& operator>>(istream& _cin, Date& d)
{_cin >> d._year;_cin >> d._month;_cin >> d._day;return _cin;
}int main()
{Date d; // 使用默认构造函数创建对象cin >> d; // 输入日期cout << d << endl; // 输出日期return 0;
}
说明:
友元函数可访问类的私有和保护成员,但不是类的成员函数
友元函数不能用const修饰
友元函数可以在类定义的任何地方声明,不受类访问限定符限制
一个函数可以是多个类的友元函数
友元函数的调用与普通函数的调用原理相同
2.友元类
class Time
{// 声明 Date是Time的友元// Date中可以访问Time的私有// 但是Time中不能访问Date的私有friend class Date;
public:Time(int hour = 0, int minute = 0, int second = 0): _hour(hour), _minute(minute), _second(second){}
private:int _hour;int _minute;int _second;
};class Date
{
public:Date(int year = 1900, int month = 1, int day = 1): _year(year), _month(month), _day(day){_t._hour++;}void SetTimeOfDate(int hour, int minute, int second){// 直接访问时间类私有的成员变量_t._hour = hour;_t._minute = minute;_t._second = second;}
private:int _year;int _month;int _day;Time _t;
};
五.内部类
class A
{
private:static int k;int h;
public:void func(){}
private:// 内部类// 独立的类,放到A里面// 仅仅受到类域限制class B // B天生就是A的友元{public:void foo(const A& a){cout << k << endl;//OKcout << a.h << endl;//OK}private:int _b;};
};int main()
{cout << sizeof(A) << endl;A a1;//A::B b1;return 0;
}
a对象里面不存在一个b对象
所以a的大小只和自己有关
我们可以在a类里面定义b这个类(B天生是A的友元),所以我们可以将B理解成为A的专属
C++不太喜欢内部类
还是这个题目,我们可以尝试使用内部类来进行解决:
https://www.nowcoder.com/practice/7a0da8fc483247ff8800059e12d7caf1?tpId=13&tqId=11200&tPage=3&rp=3&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking
class Solution {class sum{public:sum(){_ret += _i;++_i;}};static int _i;static int _ret;
public:int Sum_Solution(int n) {sum arr[n];return _ret;}
};int Solution::_i = 1;
int Solution::_ret = 0;