从C语言到C++⑦(第二章_类和对象_下篇)初始化列表+explicit+static成员+友元+内部类+匿名对象(中)

简介: 从C语言到C++⑦(第二章_类和对象_下篇)初始化列表+explicit+static成员+友元+内部类+匿名对象

从C语言到C++⑦(第二章_类和对象_下篇)初始化列表+explicit+static成员+友元+内部类+匿名对象(上):https://developer.aliyun.com/article/1513652

3.3 static成员使用场景

如果有这么一个要求:设计一个只能在栈上定义对象的类。

class StackOnly
{
public:
  StackOnly(int x = 0, int y = 0)
    :_x(x)
    , _y(0)
  {
  
  }
 
private:
  int _x = 0;
  int _y = 0;
};
 
int main()
{
  StackOnly so1; // 栈
  static StackOnly so2; // 静态区
 
  return 0;
}

怎么设计一个只能在栈上定义对象的类?

应该不让类外面的人随便调用构造函数,所以我们把构造函数设置成私有,

那就要再设计一个类内的成员函数获取在栈上定义对象的函数:

 class StackOnly
{
public:
  StackOnly CreateObj()
  {
    StackOnly so;
    return so;
  }
 
private:
  StackOnly(int x = 0, int y = 0)
    :_x(x)
    , _y(0)
  {
 
  }
 
  int _x = 0;
  int _y = 0;
};
 
int main()
{
  //StackOnly so1; // 栈
  //static StackOnly so2; // 静态区
  CreateObj();
 
  return 0;
}

现在这里的代码是过不了的,CreateObj(); 需要对象调,创造对象又要调用CreateObj();

这就是一个先有鸡还是先有蛋的问题了。

这时我们的静态成员函数就能上场了:(因为静态成员用类域也能调)

 class StackOnly
{
public:
  static StackOnly CreateObj()
  {
    StackOnly so;
    return so;
  }
 
private:
  StackOnly(int x = 0, int y = 0)
    :_x(x)
    , _y(0)
  {
 
  }
 
  int _x = 0;
  int _y = 0;
};
 
int main()
{
  //StackOnly so1; // 栈
  //static StackOnly so2; // 静态区
  StackOnly so3 = StackOnly::CreateObj();
 
  return 0;
}

(类和对象后面的OJ题还会有使用静态成员的场景)

这里有两个问题:

1. 静态成员函数可以调用非静态成员函数吗?

2. 非静态成员函数可以调用类的静态成员函数吗?

问题1是不可以的因为静态成员函数没有this指针。

问题2是可以的,因为静态的属于整个类。

4.  友元(friend)

4.1 引入:日期类的流提取

下面这个日期类,我们是调用 Print 成员函数来打印的:

#include <iostream>
using namespace std;
 
class Date 
{
public:
  Date(int year = 1, int month = 1, int day = 1) 
  {
    _year = year;
    _month = month;
    _day = day;
  }
  void Print() const 
  {
    cout << _year << "年" << _month << "月" << _day << "日" << endl;
  }
 
private:
  int _year;
  int _month;
  int _day;
};
 
int main()
{
  Date d1(2023, 5, 7);
  d1.Print();
 
  return 0;
}

我们此时思考一个问题,我们能不能用 cout 输出一下 d1 呢? cout << d1;

这样当然是不行的,主要的原因还是这个是一个操作符。

是C++里面的 流插入 ,这里的意思就是要像流里面插入一个 d1。

我们说过,内置类型是支持运算符的,而自定义类型是不支持的,

它是不知道该怎么输出的,输入也是一样的道理,也是不知道该怎么去输入。

那怎样才能向我们内置类型一样去用 流插入流提取 呢?

依然可以使用重载这个运算符的方法来解决!

cout 其实是一个全局类型的对象,这个对象的类型是 ostream :


内置类型之所以能直接支持你用,是因为 ostream 已经帮你写好了。

所谓的 "自动识别类型" ,不过只是函数重载而已……

你是 int 它就匹配 int ,你是 char 它就匹配 char

我们现在知道了, cout 是一个 ostream 类型的对象了,我们来重载一下:

第一想法是这样吗?:

#include <iostream>
using namespace std;
 
class Date 
{
public:
  Date(int year = 1, int month = 1, int day = 1) 
  {
    _year = year;
    _month = month;
    _day = day;
  }
  void Print() const 
  {
    cout << _year << "年" << _month << "月" << _day << "日" << endl;
  }
 
  void operator<<(ostream& out)
  {
    out << _year << "年" << _month << "月" << _day << "日" << endl;
  }
private:
  int _year;
  int _month;
  int _day;
};
 
int main()
{
  Date d1(2023, 5, 7);
  //d1.Print();
  cout << d1;
 
  return 0;
}

这时我们发现 cout << d1 还是识别不了,调不动。

这里不识别的原因是因为它是按参数走的,第一个参数是左操作数,第二个参数是右操作数。

双操作数的运算符重载时,规定第一个参数是左操作数,第二个参数是右操作数。

我们这里是成员函数,那第一个参数是隐含的this

所以,我们在调用这个流插入重载时就需要:

d1.operator<<(cout);

我们要直接写就会成这样:

#include <iostream>
using namespace std;
 
class Date 
{
public:
  Date(int year = 1, int month = 1, int day = 1) 
  {
    _year = year;
    _month = month;
    _day = day;
  }
  void Print() const 
  {
    cout << _year << "年" << _month << "月" << _day << "日" << endl;
  }
 
  void operator<<(ostream& out)
  {
    out << _year << "年" << _month << "月" << _day << "日" << endl;
  }
private:
  int _year;
  int _month;
  int _day;
};
 
int main()
{
  Date d1(2023, 5, 7);
  //d1.Print();
  //cout << d1;
  d1 << cout;
 
  return 0;
}

可以打印出来了,但是这样看起来就变扭了:

这不符合我们对 "流" 的理解,我们正常理解流插入,是对象流到 cout 里面去。

因为被隐含的 this 指针参数给占据了,所以就一定会是左操作数,

这时如果写成成员函数,双操作数的左操作数一定是对象。

基于这样的原因,我们如果还是想让 cout 到左边去,就不能把他重载成成员函数了。

可以直接把它重载成全局的,在类外面,不是成员函数了就没有这些隐含的东西了!

这样的话就可以让第一个参数变为左操作数,即 out 在第一个位置,Date& d 在第二个位置:

void operator<<(ostream& out, const Date& d) 
{
  out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
}

这个时候调用是肯定能调的动了,调的是全局函数。


但我们现在面临的问题是,不能访问私有的问题。


能访问私有的问题改如何解决?把 private 改为 public ?


这种方式肯定是不好的,当然我们可以写个 getYear getMonth getDay 去获取它们。


这样也可以,但是输入的时候怎么办?我们再实现 cin 流体去的时候是要 "写" 的。


这时候就麻烦了,你还得写一个 set,属实是麻烦,有没有更好地办法可以解决这种问题呢?


铺垫了这么久,终于来辣:C++ 引入了一个东西叫做 —— 友元。

4.2 友元的概念

一个全局函数想用对象去访问对象的 private 或者 protected,就可以用友元来解决。

友元分为 友元函数友元类

比如刚才我们想访问 Date 类,就可以把它定义为 友元函数 ,友元的声明要放到类里面。

需要注意的是:友元破坏了封装,能不用就不用。


从C语言到C++⑦(第二章_类和对象_下篇)初始化列表+explicit+static成员+友元+内部类+匿名对象(下):https://developer.aliyun.com/article/1513654?spm=a2c6h.13148508.setting.27.5e0d4f0eimuY68

目录
相关文章
|
30天前
|
编译器 C语言 C++
C++入门3——类与对象2-2(类的6个默认成员函数)
C++入门3——类与对象2-2(类的6个默认成员函数)
23 3
|
30天前
|
存储 编译器 C++
C++入门3——类与对象2-1(类的6个默认成员函数)
C++入门3——类与对象2-1(类的6个默认成员函数)
22 1
|
18天前
|
存储 编译器 对象存储
【C++打怪之路Lv5】-- 类和对象(下)
【C++打怪之路Lv5】-- 类和对象(下)
21 4
|
18天前
|
编译器 C语言 C++
【C++打怪之路Lv4】-- 类和对象(中)
【C++打怪之路Lv4】-- 类和对象(中)
18 4
|
18天前
|
存储 安全 C++
【C++打怪之路Lv8】-- string类
【C++打怪之路Lv8】-- string类
17 1
|
28天前
|
存储 编译器 C++
【C++类和对象(下)】——我与C++的不解之缘(五)
【C++类和对象(下)】——我与C++的不解之缘(五)
|
28天前
|
编译器 C++
【C++类和对象(中)】—— 我与C++的不解之缘(四)
【C++类和对象(中)】—— 我与C++的不解之缘(四)
|
30天前
|
存储 编译器 C语言
C++入门2——类与对象1(类的定义和this指针)
C++入门2——类与对象1(类的定义和this指针)
22 2
|
30天前
|
C++
C++番外篇——对于继承中子类与父类对象同时定义其析构顺序的探究
C++番外篇——对于继承中子类与父类对象同时定义其析构顺序的探究
51 1
|
30天前
|
编译器 C语言 C++
C++入门4——类与对象3-1(构造函数的类型转换和友元详解)
C++入门4——类与对象3-1(构造函数的类型转换和友元详解)
18 1