从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

目录
相关文章
|
7天前
|
存储 编译器 C++
【c++】类和对象(下)(取地址运算符重载、深究构造函数、类型转换、static修饰成员、友元、内部类、匿名对象)
本文介绍了C++中类和对象的高级特性,包括取地址运算符重载、构造函数的初始化列表、类型转换、static修饰成员、友元、内部类及匿名对象等内容。文章详细解释了每个概念的使用方法和注意事项,帮助读者深入了解C++面向对象编程的核心机制。
30 5
|
1月前
|
C语言 C++
C 语言的关键字 static 和 C++ 的关键字 static 有什么区别
在C语言中,`static`关键字主要用于变量声明,使得该变量的作用域被限制在其被声明的函数内部,且在整个程序运行期间保留其值。而在C++中,除了继承了C的特性外,`static`还可以用于类成员,使该成员被所有类实例共享,同时在类外进行初始化。这使得C++中的`static`具有更广泛的应用场景,不仅限于控制变量的作用域和生存期。
55 10
|
1天前
|
存储 编译器 C语言
【c++丨STL】string类的使用
本文介绍了C++中`string`类的基本概念及其主要接口。`string`类在C++标准库中扮演着重要角色,它提供了比C语言中字符串处理函数更丰富、安全和便捷的功能。文章详细讲解了`string`类的构造函数、赋值运算符、容量管理接口、元素访问及遍历方法、字符串修改操作、字符串运算接口、常量成员和非成员函数等内容。通过实例演示了如何使用这些接口进行字符串的创建、修改、查找和比较等操作,帮助读者更好地理解和掌握`string`类的应用。
13 2
|
13天前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
42 4
|
14天前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
40 4
|
1月前
|
存储 编译器 对象存储
【C++打怪之路Lv5】-- 类和对象(下)
【C++打怪之路Lv5】-- 类和对象(下)
28 4
|
1月前
|
编译器 C语言 C++
【C++打怪之路Lv4】-- 类和对象(中)
【C++打怪之路Lv4】-- 类和对象(中)
25 4
|
1月前
|
存储 安全 C++
【C++打怪之路Lv8】-- string类
【C++打怪之路Lv8】-- string类
22 1
|
1月前
|
存储 编译器 C++
【C++类和对象(下)】——我与C++的不解之缘(五)
【C++类和对象(下)】——我与C++的不解之缘(五)
|
1月前
|
编译器 C++
【C++类和对象(中)】—— 我与C++的不解之缘(四)
【C++类和对象(中)】—— 我与C++的不解之缘(四)