从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

目录
相关文章
|
5月前
|
存储 C语言
【C语言】static 关键字详解
`static` 关键字在C语言中用于控制变量和函数的作用域和生命周期。它可以用于局部变量、全局变量和函数,具有不同的效果。理解 `static` 关键字的用法有助于封装和管理代码,提高代码的可维护性和可靠性。
274 3
|
6月前
|
存储 编译器 C++
【c++】类和对象(下)(取地址运算符重载、深究构造函数、类型转换、static修饰成员、友元、内部类、匿名对象)
本文介绍了C++中类和对象的高级特性,包括取地址运算符重载、构造函数的初始化列表、类型转换、static修饰成员、友元、内部类及匿名对象等内容。文章详细解释了每个概念的使用方法和注意事项,帮助读者深入了解C++面向对象编程的核心机制。
199 5
|
7月前
|
C语言 C++
C 语言的关键字 static 和 C++ 的关键字 static 有什么区别
在C语言中,`static`关键字主要用于变量声明,使得该变量的作用域被限制在其被声明的函数内部,且在整个程序运行期间保留其值。而在C++中,除了继承了C的特性外,`static`还可以用于类成员,使该成员被所有类实例共享,同时在类外进行初始化。这使得C++中的`static`具有更广泛的应用场景,不仅限于控制变量的作用域和生存期。
164 10
|
测试技术 C语言
《测试驱动的嵌入式C语言开发》——3.3节写一个测试列表
本节书摘来自华章社区《测试驱动的嵌入式C语言开发》一书中的第3章,第3.3节写一个测试列表,作者:(美)James W. Grenning,更多章节内容可以访问云栖社区“华章社区”公众号查看
1370 0
|
4月前
|
存储 算法 C语言
【C语言程序设计——函数】素数判定(头歌实践教学平台习题)【合集】
本内容介绍了编写一个判断素数的子函数的任务,涵盖循环控制与跳转语句、算术运算符(%)、以及素数的概念。任务要求在主函数中输入整数并输出是否为素数的信息。相关知识包括 `for` 和 `while` 循环、`break` 和 `continue` 语句、取余运算符 `%` 的使用及素数定义、分布规律和应用场景。编程要求根据提示补充代码,测试说明提供了输入输出示例,最后给出通关代码和测试结果。 任务核心:编写判断素数的子函数并在主函数中调用,涉及循环结构和条件判断。
226 23
|
3月前
|
人工智能 Java 程序员
一文彻底搞清楚C语言的函数
本文介绍C语言函数:函数是程序模块化的工具,由函数头和函数体组成,涵盖定义、调用、参数传递及声明等内容。值传递确保实参不受影响,函数声明增强代码可读性。君志所向,一往无前!
47 1
一文彻底搞清楚C语言的函数
|
4月前
|
算法 C语言
【C语言程序设计——函数】利用函数求解最大公约数和最小公倍数(头歌实践教学平台习题)【合集】
本文档介绍了如何编写两个子函数,分别求任意两个整数的最大公约数和最小公倍数。内容涵盖循环控制与跳转语句的使用、最大公约数的求法(包括辗转相除法和更相减损术),以及基于最大公约数求最小公倍数的方法。通过示例代码和测试说明,帮助读者理解和实现相关算法。最终提供了完整的通关代码及测试结果,确保编程任务的成功完成。
198 15
【C语言程序设计——函数】利用函数求解最大公约数和最小公倍数(头歌实践教学平台习题)【合集】
|
4月前
|
C语言
【C语言程序设计——函数】亲密数判定(头歌实践教学平台习题)【合集】
本文介绍了通过编程实现打印3000以内的全部亲密数的任务。主要内容包括: 1. **任务描述**:实现函数打印3000以内的全部亲密数。 2. **相关知识**: - 循环控制和跳转语句(for、while循环,break、continue语句)的使用。 - 亲密数的概念及历史背景。 - 判断亲密数的方法:计算数A的因子和存于B,再计算B的因子和存于sum,最后比较sum与A是否相等。 3. **编程要求**:根据提示在指定区域内补充代码。 4. **测试说明**:平台对代码进行测试,预期输出如220和284是一组亲密数。 5. **通关代码**:提供了完整的C语言代码实现
102 24
|
4月前
|
存储 C语言
【C语言程序设计——函数】递归求斐波那契数列的前n项(头歌实践教学平台习题)【合集】
本关任务是编写递归函数求斐波那契数列的前n项。主要内容包括: 1. **递归的概念**:递归是一种函数直接或间接调用自身的编程技巧,通过“俄罗斯套娃”的方式解决问题。 2. **边界条件的确定**:边界条件是递归停止的条件,确保递归不会无限进行。例如,计算阶乘时,当n为0或1时返回1。 3. **循环控制与跳转语句**:介绍`for`、`while`循环及`break`、`continue`语句的使用方法。 编程要求是在右侧编辑器Begin--End之间补充代码,测试输入分别为3和5,预期输出为斐波那契数列的前几项。通关代码已给出,需确保正确实现递归逻辑并处理好边界条件,以避免栈溢出或结果
219 16
|
4月前
|
存储 编译器 C语言
【C语言程序设计——函数】分数数列求和2(头歌实践教学平台习题)【合集】
函数首部:按照 C 语言语法,函数的定义首部表明这是一个自定义函数,函数名为fun,它接收一个整型参数n,用于指定要求阶乘的那个数,并且函数的返回值类型为float(在实际中如果阶乘结果数值较大,用float可能会有精度损失,也可以考虑使用double等更合适的数据类型,这里以float为例)。例如:// 函数体代码将放在这里函数体内部变量定义:在函数体中,首先需要定义一些变量来辅助完成阶乘的计算。比如需要定义一个变量(通常为float或double类型,这里假设用float。
114 3

热门文章

最新文章