自学内容网 自学内容网

C++基础速通笔记-下(持续补充)

2024/11/4 开写

速通笔记-上:C++基础速通笔记-上(持续补充)-CSDN博客

一、虚函数

1.1虚函数与多态

虚函数

  • 虚函数在基类中声明(在基类函数的声明前加上virtual关键字即可),并且在派生类中可以被重写(Override)
  • Son类中的print()函数没有virtual关键字,但因为它在基类Parent中已经被声明为虚函数,所以它自动成为虚函数。

动态多态性

  • 动态多态性是通过 虚函数 指针引用 实现的。在运行时,根据对象的实际类型来决定调用哪个函数。
  • main()函数中,通过基类指针调用虚函数:(                                                                              ·首先,pparent是一个指向Parent类对象的指针,调用pparent->print()会调用Parent类的print()函数,因为pparent实际指向的是Parent类的对象。                                                        ·然后,parent是一个指向Parent类对象的指针,但它实际上指向的是Son类的对象(通过new Son()创建)。当调用parent->print()时,由于print()是虚函数,C++ 会根据对象的实际类型(即Son类)来决定调用Son类的print()函数。)

输出:

*注:
·构造函数不能是虚函数,因为构造函数执行时,对象的类型尚未完全确定(即对象还未完全构造出来。构造函数的目的是初始化对象,而虚函数的目的是实现多态行为。

1.2抽象类与纯虚函数

·抽象类:类中的每个函数前面都有virtual

        ·抽象类的主要作用是作为一个基类,用于定义一组接口(即一组纯虚函数),这些接口将由派生类通过提供具体实现来继承和实现。由于抽象类不能实例化(不能用来创建对象),它通常用作基类来指导派生类的设计和实现。

        ·抽象类通常用于定义一组操作的规范,这些操作将由派生类根据具体情况来实现。

·纯虚函数:类里面的函数就是纯虚函数,函数声明的末尾加上= 0。它不需要(也不允许)在基类中提供实现。

                ·纯虚函数是面向对象编程中用于定义接口的一个关键概念。主要目的是强制要求从该类派生的 所有子类 都必须提供该函数的具体实现,从而确保派生类具有特定的行为或功能

*注:

·静态数据是继承的

·虚函数被继承后依然是虚函数

Human* man = new Man;

这里使用了基类指针Human*来指向派生类对象Man,这是多态性的体现

运行结果

  • 当运行这段代码时,首先会调用man->empty(),输出empty并返回 0。
  • 然后调用man->print(),输出输出
empty
输出

1.3虚析构函数

        为了析构完整,给析构函数加上virtual,不然出现内存问题——因为如果析构函数不是虚函数,当通过Parent*指针删除对象时,只会调用Parent类的析构函数,而不会调用Son类的析构函数。

输出:

*一些补充

Parent* parent = new Son; :

是一种常见的涉及多态和继承的操作——声明了一个指向Parent类型的指针parent,然后通过new操作符创建了一个Son类的对象,并将这个对象的地址赋给parent指针。

内存分配

  • new Son:这部分在堆上分配了足够的内存来存储一个Son类的对象,并调用Son类的构造函数来初始化这个对象。(这块内存空间的大小是由Son类的大小决定的,它包括Son类自身定义的数据成员所占用的空间,以及从Parent类(如果Son继承自Parent)继承来的数据成员所占用的空间。

 对象创建

  • 在分配内存之后,new操作符会调用Son类的构造函数来初始化这块内存中的对象。如果Son类没有显式定义构造函数,编译器会提供一个默认构造函数。这个默认构造函数会按照数据成员的声明顺序来初始化它们。
  • 由于Son类继承自Parent类,在创建Son类对象时,会先调用Parent类的构造函数。在Parent类中
  • 如果Son类有自己的构造函数,这个构造函数会执行用户定义的初始化操作。例如,如果Son类的构造函数接受参数来初始化sonData,那么在创建对象时可以这样写:Son* son = new Son(5);,这里5就是传递给构造函数用来初始化sonData的参数。
  • new操作符返回的是一个指向新创建对象的指针。在Parent* parent = new Son;这个语句中,虽然声明的指针类型是Parent*,但实际上这个指针指向的是一个Son类的对象。这个指针可以用来操作对象。由于SonParent的子类,通过Parent*指针可以调用Parent类中定义的函数(如果这些函数不是纯虚函数),也可以调用Son类中重写的函数(如果存在多态)——如果派生类没有重写纯虚函数,那么试图通过基类指针调用纯虚函数会导致编译错误(因为抽象类的纯虚函数不能被调用,除非在派生类中实现了它)
  • 由于SonParent的子类,Son类的对象通常会包含Parent类的所有成员(加上Son类自己特有的成员)
  • 总结:当执行new Son时,首先会调用Parent类的构造函数,然后调用Son类的构造函数。这样确保了对象的所有数据成员(包括从基类继承的数据成员)都被正确初始化——new Son操作就是在堆内存中分配空间、构造Son类的对象,并返回指向该对象的指针。

多态行为

  • 这种赋值方式允许通过Parent类型的指针来操作Son类的对象,这是多态的基础。
  • 例如,如果Parent类有一个虚函数void doSomething(),并且Son类重写了这个虚函数,那么通过parent指针调用doSomething()时,会根据对象的实际类型(这里是Son)来调用Son类中的doSomething()实现,而不是Parent类中的实现。
  • 代码中的多态性通过虚函数的定义和重写,以及通过基类指针调用这些虚函数来实现。这种机制使得程序能够在运行时根据 对象的实际类型 来决定调用哪个函数,从而实现了不同对象对同一函数调用做出不同响应的多态行为。

二、IO流

2.1介绍

2.2IO流类

2.3IO流对象

2.4C++提供的函数

更多详解:C++IO流详解_c++的流是什么-CSDN博客

1.示例1:标准输入输出流

#include <iostream>
#define _CRT_SECURE_NO_WARNINGS
int main() {
    char name[5];
    std::cin >> name;
    std::cout << name << std::endl;
    // 处理输入缓冲区
    std::cin.ignore();
    std::cout << "请重新输入:";
    std::cin.get(name, 5);
    std::cout << name << std::endl;
    return 0;
}

 输出:

test
请重新输入:
more

处理输入缓冲区

std::cin.ignore();用于忽略输入缓冲区中的一个字符。通常在这里用于处理第一次输入后可能残留的换行符等字符,以确保后续的输入操作不受干扰。

第二次输入和输出(避免潜在问题)

std::cin.get(name, 5);再次从标准输入读取最多 4 个字符到name数组中。

  • 区别一:对空白字符的处理

    • std::cin >> name在读取输入时,遇到空白字符(如空格、制表符、换行符)会停止读取。
    • 例如,如果用户输入 “a b c”,使用std::cin >> name只会读取 “a” 并存储到name数组中。
    • std::cin.get(name, 5)会读取包括空白字符在内的最多 4 个字符。如果用户输入 “a b c”,使用std::cin.get(name, 5)会读取 “a b c”(如果输入的字符总数不超过 4 个)并存储到name数组中。
  • 区别二:缓冲区处理

    • std::cin >> name在某些情况下可能会在输入缓冲区中留下未处理的字符,尤其是当输入的内容超出了目标变量的存储能力时。
    • 例如,如果用户输入了一个较长的字符串,超出了name数组的长度,std::cin >> name可能只会读取部分字符并存储到name数组中,但输入缓冲区中可能还残留着未被读取的字符。这导致后续的输入操作受到影响。
    • std::cin.get(name, 5)在处理缓冲区方面更加稳健。它可以确保读取指定数量的字符,并在必要时处理输入缓冲区中的剩余字符。例如,如果用户输入了一个较长的字符串,超出了name数组的长度,std::cin.get(name, 5)会读取前 4 个字符并存储到name数组中,同时会正确处理输入缓冲区中的剩余字符,避免对后续的输入操作产生干扰。

重载形式的提供者 

 C++ 标准库中,istream类(cinistream类的一个对象)的开发者提供了cin.get的多种重载形式。这些重载函数是 C++ 标准库已经定义好的。作为库的使用者,只需要根据自己的需求调用合适的cin.get重载形式即可

cin.get重载

  cin.get有多种重载形式,其中与cin.get(name, 5)相关的重载形式是istream& get(char* s, streamsize n);

        这里char* s表示指向字符数组的指针,streamsize n表示要读取的最大字符数——这个函数的功能是从输入流中读取最多 n - 1 个字符到 所指向的字符数组中,并自动在末尾添加 '\0' 以形成 C 风格字符串。它返回对输入流的引用,这使得可以进行 链式调用。

与其他重载形式的对比

·与int get();对比

  • int get();这个重载形式是从输入流中读取一个字符,并返回其 ASCII 码值。如果遇到文件末尾(EOF),则返回EOF(通常是 - 1)。它主要用于逐个字符的读取——而cin.get(name, 5)是用于 读取多个字符 并 形成字符串。
  • 例如,int ch = cin.get();是读取一个字符并获取其 ASCII 码,而cin.get(name, 5)是读取多个字符到一个数组

·与istream& get(char& c);对比

  • istream& get(char& c);这个重载形式是读取一个字符并存储到引用参数c所指向的变量中。它主要用于读取单个字符并存储到指定的 变量 ——而cin.get(name, 5)是用于读取多个字符到 一个数组 。
  • 例如,char ch; cin.get(ch);是读取一个字符存储到ch变量中,而cin.get(name, 5)是读取多个字符存储到name数组中。

函数名相同

        cin.get的不同形式都使用了相同的函数名get。在 C++ 中,只要函数名相同并且在同一个作用域内,就有可能是重载。

*(总结)参数列表不同

  • int get();:没有参数,用于获取一个字符的 ASCII 码值。
  • istream& get(char& c);:有一个char类型的引用参数,用于将读取的一个字符存储到该引用所指向的变量中。
  • istream& get(char* s, streamsize n);:有一个char指针参数和一个表示数量的streamsize参数,用于将多个字符读取到字符数组中。
  • 这些不同的参数列表符合 C++ 中函数重载的定义。函数重载允许在同一个类(istream类)中定义多个同名函数,只要它们的参数列表在参数个数、参数类型或者参数顺序上有所不同。在处理不同类型的用户输入(如单个字符选择、字符串输入等)时,可以根据具体情况选择最合适的cin.get重载形式来确保正确的输入操作。

*注:

·当写成std::cin时,std是命名空间的名称。::是作用域解析运算符,用于指定cinstd命名空间中。这种写法明确地指出了cin的来源,即它是std命名空间中的cin对象。(使用using namespace std;时省略

2.示例2:格式控制符

cout<<setiosflages(ios::left) //左对齐
    <<setw(8)<<"姓名" //"姓名在左对齐占8个字节"
    <<setw(8)<<"性别"
    <<setw(8)<<"年龄"<<endl;

三、文件操作

3.1关于文件的头文件

3.2文件基本操作的函数

3.3文件操作初级

#include "标头.h"
#include<fstream> //文件可读可写的类

int main() {
    //打开文件
    fstream file;

    file.open("mm.text", ios::out | ios::in | ios::trunc); //ios::app追加
    //写文件
    file << "Loveyou" << " " << 1001;

    //读文件
    file.seekg(ios::beg);
    char str[10];
    int num;
    file >> str >> num;
    cout << str << ":" << num << endl;

    //关闭文件
    file.close();
    return 0;
}

流程:

头文件

  • #include <fstream>:这个头文件提供了对 文件输入 / 输出流 的支持,用于操作文件。
  • #include "标头.h":这里引用了一个 自定义的头文件 ,可能包含了一些程序需要的声明或定义。

 文件流对象

  • fstream file;:定义了一个fstream类型的对象filefstream是一个既能进行文件输入又能进行文件输出的文件流类。

打开文件

  • ios::out:以输出模式打开文件,用于写入数据。
  • ios::in:以输入模式打开文件,用于读取数据。
  • ios::trunc:如果文件存在,截断文件(删除原有内容)。如果文件不存在,则创建新文件。
  • 注:这里的ios::app是追加模式,在原代码中被注释掉了。如果使用ios::app,数据将被追加到文件末尾,而不是覆盖原有内容。

写入文件

  • 使用<<运算符向文件中写入数据。这里写入了字符串"Loveyou"和整数1001,中间用空格隔开。

读取文件

  • file.seekg(ios::beg);
    • seekg函数用于设置文件读取位置。ios::beg表示将读取位置设置到文件开头。
  • char str[10];int num;
    • 定义了一个字符数组str和一个整数num,用于存储从文件中读取的数据。
  • file >> str >> num;
    • 使用>>运算符从文件中读取数据。先读取字符串到str中,再读取整数到num中。

关闭文件

  • 关闭文件流。用于释放与文件相关的资源。

*注:

  1. ios::trunc的作用

    • 文件存在时
      • 当使用ios::trunc模式打开文件时,如果文件已经存在,那么文件中的原有内容将被删除,文件大小变为 0 字节。这就像是把文件 “截断” 了,只保留了文件的元信息(如文件名、权限等),而文件中的数据被清空。
    • 文件不存在时
      • 如果文件不存在,使用ios::trunc模式打开文件会创建一个新的空文件。这是因为ios::trunc模式本身包含了创建文件的功能,当找不到文件时,它会创建一个新的文件来满足后续的操作需求。
    • 示例代码中的体现
      • file.open("mm.text", ios::out | ios::in | ios::trunc);这行代码中,ios::truncios::out(输出模式)和ios::in(输入模式)一起使用。这意味着程序试图以既可以读又可以写的方式打开mm.text文件,并且如果文件存在,会先清空文件内容。
  2. ios::app的作用

    • 追加模式
      • 当使用ios::app模式打开文件时,无论文件是否存在,所有写入文件的操作都会将数据追加到文件的末尾。这意味着不会覆盖文件中已有的内容。
      • 例如,如果文件中已经有了一些数据,使用ios::app模式打开后写入新的数据,新数据会被添加到文件原数据的后面。
    • ios::trunc的对比
      • ios::trunc不同,ios::app不会删除文件中的原有内容。ios::trunc是先清空文件再进行操作,而ios::app是在文件现有内容的基础上添加新内容。
    • 示例代码中的体现
      • 在原代码中ios::app被注释掉了。如果取消注释并使用ios::app代替ios::trunc,即file.open("mm.text", ios::out | ios::in | ios::app);,那么每次向文件写入数据时,数据都会被添加到文件的末尾,而不会影响文件中已有的内容。

 3.4文件操作

 1.整体输入到文件中

有一个类,类里面很多参数,要求整体输出

#include <iostream>
#include <string>
#include <fstream>
#include <iomanip>
using namespace std;

struct MM {
    string name;
    int num;
    int age;
};

int main() {
    MM mm[3] = {{"name1", 10, 1001}, {"name2", 10, 1002}, {"name3", 10, 1003}};

    // 打开文件
    fstream file;
    file.open("mm.text", ios::out | ios::in | ios::trunc);

    // 写文件
    file.write((char*)&mm[0], sizeof(struct MM) * 3);

    // 读文件
    file.seekg(0, ios::beg);
    MM object[3];
    file.read((char*)&object[0], sizeof(struct MM) * 3);

    for (int i = 0; i < 3; i++) {
        cout << setiosflags(ios::left) << setw(10)  //输出格式
                                ,使name左对齐并占据 10 个字符宽度
             << object[i].name << object[i].age << object[i].num << endl;
    }

    // 关闭文件
    file.close();
    system("pause");//暂停程序,等待用户输入。
            这在某些编译器环境下可以让用户看到程序的输出结果后再关闭窗口
    return 0;
}

 

 流程分析:

头文件

  • #include <iostream>:用于支持输入 / 输出操作,如cout
  • #include <string>:用于支持string类型,在结构体MM中使用了string来存储名字。
  • #include <fstream>:提供文件流操作的支持,用于读写文件。
  • #include <iomanip>:用于格式化输出,如setiosflagssetw

主函数中的操作 

  • main函数中,定义并初始化了一个MM类型的数组mm(初始化结构体)

文件打开

写入文件

file.write((char*)&mm[0], sizeof(struct MM) * 3);
  • write函数用于将内存中的数据块写入文件。第一个参数(char*)&mm[0]mm数组的首地址转换为char*类型,这是因为write函数要求参数是char*类型。第二个参数sizeof(struct MM) * 3表示要写入的数据块大小,这里是三个MM结构体的大小。

 读取文件

  • seekg函数用于设置文件读取位置,0表示偏移量,ios::beg表示从文件开头开始偏移。
  • 然后定义了一个新的MM类型数组object(新建结构体),并使用read函数从文件中读取数据(接收文件mm.text里的东西)
  • read函数用于从文件中读取数据块到内存中,参数的含义与write函数类似。

*注:

file.write((char*)&mm[0], sizeof(struct MM) * 3);

含义:将mm数组中的三个MM结构体元素的数据写入到与file对象相关联的文件中。 

  • write函数是fstream类中的一个成员函数,用于将数据块写入文件。其函数原型为ostream& write(const char* s, streamsize n);,其中s是指向要写入数据的指针,n是要写入的字节数。
  • 参数分析
    • (char*)&mm[0]
      • mm是一个MM类型的数组,&mm[0]获取数组mm的第一个元素的地址。由于write函数要求数据以char*类型的指针传入,所以需要将&mm[0]强制转换为char*类型。这是因为在 C++ 中,char*类型的指针可以用来操作内存块。
    • sizeof(struct MM) * 3
      • sizeof(struct MM)计算 MM结构体 的大小(以字节为单位)。这里MM结构体包含一个string类型的name、一个int类型的num和一个int类型的age。由于string类型内部有自己的存储机制,sizeof(string)的值可能会因编译器和平台而异,但这里主要关注的是整个MM结构体的大小
      • 乘以3表示要写入的数据量是三个MM结构体的大小,即把数组mm中的三个元素都写入文件。
 file.read((char*)&object[0], sizeof(struct MM) * 3);

 含义:从与file对象相关联的文件中读取三个MM结构体大小的数据,并存储到object数组中。

  • read函数也是fstream类中的成员函数,用于从文件中读取数据块。其函数原型为istream& read(char* s, streamsize n);,其中s是指向存储读取数据的内存位置的指针,n是要读取的字节数。
  • 其他同理。

四、异常处理

1.1异常的处理

·throw 抛出想要抛出的东西

·try{ 要运行的原函数 }

·catch( 接受的throw内容 ){ 异常处理 }——catch块根据异常类型来捕获异常

#include <iostream>
#include <string>
using namespace std;

void printError(int a, int b) {
    if (b == 0) {
        throw b;  //抛出b(int类型)作为异常
    }
    cout << a << " / " << b << endl;
}

void printError(int a) {
    if (a == 0) {
        throw string("Love");//函数抛出一个string类型的对象作为异常
    }
}

int main() {
    print(2, 0);
    try {
        printError(2, 0);
    }
    catch (int a) {
        cout << a << " 竟然是零" << endl;
    }
    try {
        printError(0);
    }
    catch (string name) {
        cout << "Ilove" << name << endl;
    }
    return 0;
}

异常类型的匹配

  • 首先调用printError(2, 0),触发int类型异常,被catch (int a)捕获并输出0 竟然是零
  • 然后调用printError(0),触发string类型异常,被catch (string name)捕获并输出IloveLove

异常规格说明:throw() 

在以上代码加上这部分:

//不可能有错误的函数
int max(int a, int b)throw(){
    return a > b ? a : b;
}

输出:

 

  • 函数声明中的throw()是异常规格说明。它表示这个函数承诺不会抛出任何异常。
  • 如果没有throw()声明,函数默认可以抛出任何类型的异常。调用者在调用这样的函数时,可能需要使用try - catch块来捕获可能出现的异常
  • 这种函数声明方式确保了函数的可靠性和可预测性,适合对异常处理有严格要求的编程场景。

五、模板 

5.1模板的起源

       C++中除了有面向对象的编程思想之外,还有一种——泛型编程,主要是通过模板技术实现,模板是泛型程序设计的基础(泛型generic type——通用类型之意)。

5.2模板定义

详解:C++模板详解-CSDN博客

·模板:建立通用的摸具,大大提高复用性(将类型参数化)。允许程序员编写通用代码以处理多种 数据类型数据结构,而不需要为每种 特定类型 编写重复的代码

·模板的特点:

        ·模板不可以直接使用,他只是一个框架

        ·模板的通用并不是万能的

*注:

·重载是参数不同,函数体也不同;模板仅仅是参数数据类型不同,函数体逻辑是相同的

5.3基本语法

1.函数模板

·建立一个通用函数,其函数返回值类型形参类型可以是不具体制定的,用一个虚拟的类型来代表

template<typename T>

·template——声明模板类型

·typename——表明其后面的符号是一种数据类型,可以用class代替

·T——通用的数据类型,名称可以替换,通常为大写字母

template <typename 形参名, typename 形参名...>     //模板头(模板说明)

返回值类型  函数名(参数列表)                   //函数定义

{

        函数体;

}

实现的两种方式:

//1.自动类型推导
mySwap(a,b);

//2.显示指定类型
mySwap<int>(a,b);

2.函数模板与普通函数区别 

函数模板不允许自动类型转化,普通函数则能进行自动类型转换

3.模板与重载的结合

#include <iostream>
#include <string>
using namespace std;

class MM {
public:
    friend ostream& operator<<(ostream& out, MM object) {
        out << object.name << " : " << object.age << endl;
        return out;
    }

    MM() {}
    MM(string name, int age) : name(name), age(age) {}

protected:
    int age;
    string name;
};

template<class typeone>
void print(typeone data) {
    cout << data;
}

int main() {
    MM mm = {"女神", 18};
    print<MM>(mm);//<MM>可省略不写
    return 0;
}

输出:

女神:18

结合方式

  • main函数中,调用了print(mm),这里mmMM类的对象。print是模板函数接受MM类对象作为参数。
  • print函数处理MM类对象mm时,实际上是调用了MM类中重载的<<操作符。因为在print函数内部,cout << data;语句中的dataMM类对象时,会触发<<操作符的重载函数。
  • 重载的<<操作符负责将MM类对象的nameage按照特定格式(name : age)输出到控制台。
  • 总结:模板函数print提供了一个通用的输出框架,能够处理不同类型的数据,通过操作符重载,使其在处理MM类对象时能够 进行特定的、符合需求的输出操作

4.模板类 

·建立一个通用类,类中的成员 数据类型 可以不具体制定,用一个 虚拟的类型 来代表

定义:

template<typename 形参名,typename 形参名…>

class 类名

{

        ………

}

示例:

template<class NameType, class AgeType>

class 类名{
public:
    类名(NameType name, AgeType age){
        this->n = name; //将构造函数的参数name的值赋给成员变量n
        this->a = age;
    }
    
    void showPerson(){
        cout<<"name:"<<this->n<<"age:"<<this->a<<endl;
    }

    NameType n;
    AgeType a;
};

使用:

void test(){
    类名<string, int> p1("小美", 18);
    p1.showPerson();
}

int main(){
    test();
    return 0;
}

·在写函数、他的继承、继承的函数时,都要声明template,因此很麻烦

示例:

template <class A, class B>
class parent
{
public:
    void print();
protected:
};

template <class A, class B>//该函数在类外定义,用于输出 "父类"
void parent<A, B>::print()
{
    cout << "父类" << endl;
}

template <class A, class B>
class son : public parent<A, B>
{
public:
    void printSon();
protected:
};

template <class A, class B>
void son<A, B>::printSon()
{
    cout << "子类" << endl;//继承自parent类
        并且有一个成员函数printSon,该函数在类外定义,用于输出 "子类"
}

int main()
{
    return 0;
}

六、迭代器

6.1迭代器

*引言(迭代器):

迭代器是一个设计概念,用于在数据结构(如链表、数组、向量等)上实现一种统一的 访问 和 遍历 方式。它是指针的一种抽象,更方便、安全地用于访问容器中的元素。它为我们提供了指向容器或数据流中元素的“指针”。虽然迭代器在行为上类似指针(可以通过 * 访问元素,通过 ++ 移动到下一个元素),但实际上它是一个对象,提供了指向数据的接口,而不是真正的指针。

在这个代码中,iterator 类的作用是提供一种标准化的方式来遍历链表 list。具体而言,它模仿指针的行为,使用户能够通过 *++!= 等操作符来访问链表中的每个节点的数据和指向下一个节点。

迭代器的本质和作用

  1. 抽象指针行为:迭代器可以看作一种智能指针。通过封装指针的行为,使用户无需直接操作内存指针,可以更安全地访问链表中的元素。

  2. 提供统一接口:迭代器为链表提供了 begin()end() 函数,用于确定遍历的起点和终点。这种接口符合 C++ 标准库的惯例,使代码在形式上与其他容器(如 std::vectorstd::list)的使用方式一致。

  3. 支持容器遍历iterator定义了常用的运算符。如 * 用于访问数据、++ 用于移动到下一个节点、!= 用于判断是否到达链表尾部。通过这些操作,迭代器能从头到尾逐一访问链表中的元素,使遍历过程更简单直观。

详细说明:C++ 迭代器(iterator)超详解+实例演练_c++ iterator-CSDN博客

C++之迭代器_c++迭代器-CSDN博客

·迭代器:用来遍历容器——访问容器数据

示例:list链表

#include"标头.h"
#include<list>
int main(){
    int array[6] = {1,2,3,4,5,6};//创建数组,初始化链表
    list<int> myList;  //链表模板是int,名字是myList(初始化容器)
    mylist.assign(array, array + 6); //初始化使用他的成员函数(开头,结尾)

    list<int>::iterator iter;  //创建正向迭代器(正着输出)
    for(iter=myList.begin(); iter != myList.end(); iter++){
        cout<<*iter<<"\t";
    }

    list<int>::reverse_iterator rIter; //反向  
    for(iter=myList.rbegin(); rIter != myList.rend(); rIter++){
        cout<<*rIter<<"\t";
    } 
    cout<<endl;
    return 0;
}

解释:

链表创建

  • list<int> myList;:创建了一个存储int类型元素的std::list(链表)。
  • myList.assign(array, array + 6);:使用assign函数将数组array中的元素复制到myList中。array是数组首地址,array + 6是指向数组末尾(最后一个元素的下一个位置)的指针。

正向迭代器 

  • myList.begin()返回指向链表第一个元素的迭代器。
  • myList.end()返回指向链表末尾(最后一个元素的下一个位置)的迭代器。
  • 在循环中,通过*iter解引用迭代器来获取当前迭代器指向的元素,并输出到控制台,元素之间用制表符\t分隔。

输出:

1    2    3    4    5    6
6    5    4    3    2    1

 6.2实现迭代器

详细:【C++ 迭代器实现 终篇】深入理解C++自定义容器和迭代器的实现与应用_c++ 自定义迭代器-CSDN博客

 去掉#include<list>,自己去写,以下代码的更多细节说明:C++ 链表的实现-CSDN博客

实现:

#include"标头.h"

int main(){

    int array[6] = {1,2,3,4,5,6};
    list<int> myList;  
    mylist.assign(array, array + 6);
    list<int>::iterator iter;  //创建正向迭代器(正着输出)
    for(iter=myList.begin(); iter != myList.end(); iter++){
        cout<<*iter<<"\t";
    }

    cout<<endl;
    return 0;
}

1.实现myList.assign

class list:定义链表类,包含链表的头、尾指针、大小及常用的操作。封装链表操作和管理数据,方便外部使用链表。

(均是写在public中)

#include<iostream>
#include<string>
using namespace std;
template<class type>
//做一个节点
struct Node{
    int data;
    Node<type>* next;
//写构造函数
    Node() :next(nullptr){} //next用空来初始化,避免意外访问无效内存
    Node(type data) :data(data), next(nullptr){}
};

template<class type>
class list{
public:
    //无参构造
    list(){
        size = 0;
        listEnd = nullptr;
        listHead = nullptr;
    }
    //其次,看第一个(assign)
    void assign(int* begin, int* end){
        while(begin != end){
            Node<type>* node = new Node<type>(*begin);
            if(size == 0){
                listHead = node;
                listEnd = node->next;
            }
            else{
                node->next = listHead;
                listHead = node; // 更新 listHead 为新的头节点
            }
            size++;
            begin++;
        }
    }
protected:
//三个成员变量
    Node<type>* listHead;//链表头指针
    Node<type>* listEnd;//链表尾指针
    int size;//存储列表的大小
}

正序构建链表:

else{
            listEnd->next = node;  // 将新节点加到链表的末尾
                    //让当前链表的尾节点 listEnd 的 next 指针指向新的节点 node,将新节点连接到链表的末尾。
            listEnd = node;        // 更新 listEnd 为新的末尾
        }

节点模板结构体

  • 定义了一个模板结构体Node,用于表示链表中的节点。这里的type是一个模板参数,可以在实例化这个结构体时指定节点存储的数据类型。Node<type>*就是一个指向这种类型节点的指针。next成员变量:将当前节点与链表中的下一个节点关联起来。

  • 有两个构造函数:

    • 第一个构造函数Node()是默认构造函数,将next指针初始化为nullptr,表示该节点没有下一个连接的节点。
    • 第二个构造函数Node(type data)用于创建一个带有数据的节点,将传入的数据赋值给data成员,并将next指针初始化为nullptr,,表示新创建的节点在初始状态下不指向任何其他节点。

链表模板类 

  • 定义了一个模板类list,用于表示链表。
  • 无参构造函数list()
    • 将链表的大小size初始化为 0,表示链表初始为空。
    • listEndlistHead都初始化为nullptr,意味着链表的头和尾都为空。

assign函数

提供一种简单的方式将数组元素添加到链表中。从数组 beginend 的区间构造链表,将数据从数组逆序插入链表。

  • assign函数用于从一个整数数组(通过指针beginend来界定范围)来初始化链表。
  • 创建一个新节点 node,使用数组当前元素 *begin
    • 在循环中,只要begin不等于end,就执行以下操作:
      • 创建一个新的节点node,并使用*begin(当前指针指向的整数值)作为数据来初始化节点。
      • 如果链表大小size为 0,说明链表为空,此时将新节点同时设置为链表的头和尾(listHead = node; listEnd = node;)。
      • 如果链表大小不为 0,将新创建的节点 nodenext 指针指向当前的头节点 listHeadnode->next = listHead;),然后将新节点设置为链表的尾节点(listHead = node;)。这实际上是在创建一个逆序的链表,每次插入的节点都成为新的头节点。
      • 增加链表的大小size,并将指针begin向后移动一位。

类的保护成员

        头节点、尾节点、节点数

2.实现iterator

class iterator:定义链表的迭代器类,实现遍历链表的数据访问。提供遍历链表的标准方式,符合 C++ 容器标准的迭代器操作

class iterator{
public:
    iterator() :pmove(nullptr) {}
//用于创建一个指向 nullptr 的迭代器对象。即此迭代器指针初始为空,不指向任何节点
//当我们需要一个空的迭代器时(例如,作为链表末尾的标识),可以使用此构造函数初始化迭代器

    iterator(Node<T>* node) : pmove(node) {}
//将迭代器初始化为指向 链表中的特定节点 的指针 node
//允许通过传入一个节点指针创建迭代器,使迭代器可以直接指向特定的链表节点。比如,在 begin() 方法中调用这个构造函数以生成指向 listHead 的迭代器。

protected:
    Node<type>* pmove; //定义一个pmove的指针,指向当前节点,用于遍历链表
}

iterator(Node<T>* node) : pmove(node) {}

作用:是为迭代器 iter 提供一个初始化方法,使 iter 可以指向链表中的某个节点,从而实现遍历链表的功能。具体来说,初始化迭代器位置:该构造函数在 begin()end() 函数中被调用,用于将 iterator 指向链表的起始节点或尾后节点。实现链表遍历:将迭代器 iter 初始化为链表的起点或尾后节点,通过 for 循环和 operator++,逐个访问链表中的数据。

目的:让迭代器指向链表中的某个特定节点,通常是链表的 listHead 或者 nullptr(表示遍历结束)。在这里:

  • iterator 类的 pmove 成员保存了当前节点的指针,控制了当前访问的位置。
  • iterator 被初始化为 begin() 时,pmove 指向链表的头节点 listHead
  • iterator 被初始化为 end() 时,pmove 指向 nullptr,表示链表的终点。

这样一来,通过这个构造函数,迭代器便可用来遍历整个链表。 

3.实现begin()、end()

实现了一个链表的 beginend 方法,通过返回指针来支持链表的遍历操作。这两种方法在链表类中定义,用于获取链表的起始和终止位置。

//begin(),返回指向链表头节点的迭代器。便于从链表头开始遍历链表。
Node<type>* begin(){
    return this->listHead;
}
//end(),返回指向链表尾后位置的迭代器(此处使用空指针表示)符合 C++ 容器标准,指向尾后元素代表链表终点,用于结束遍历
Node<type>* end(){
    return this->listEnd;
}

4.实现运算符

class iterator{
public:    

    iterator() :pmove(nullptr) {}
//实现=
    void operator=(Node<type>* head){
        this->pmove = head;
    }
实现!=
    bool operator=(Node<type>* end){
        return this->pmove != end;
    }
//实现++
    iterator& operator++(int){
        this->pmove = this->pmove->next;
        return (*this);
    }
// 重载 * 运算符,用于解引用
    T& operator*() {
        return pmove->data;
    }
protected:
    Node<type>* pmove; //定义一个私有成员变量 pmove,它是指向当前节点的指针,指示当前迭代器的“位置”。pmove 用于存储当前迭代器的位置,使迭代器能够通过节点指针顺序访问链表中的每个节点。
}

5.实现输出

事先说明:

在 C++ 中,cout是标准输出流对象,它属于ostream类。从本质上来说,cout在使用时的行为类似于一个函数调用,但它实际上是一个对象。

cout对象重载了<<运算符,使得我们可以像函数调用一样使用它。例如,对于cout << 123;ostream类中会有一个类似ostream& operator<<(int value)的重载函数被调用

//接在class iteratord的public中写
type operator*(){
    return this->pmove->data;//cout对象调用<<*iter函数
}

模板和重载的混合

6.完整代码

#include <iostream>
#include <string>
using namespace std;

template<class T>
// 定义节点结构
struct Node {
    T data;
    Node<T>* next;

    // 构造函数
    Node() : next(nullptr) {} 
    Node(T data) : data(data), next(nullptr) {}
};

template<class T>
class list {
public:
    // 无参构造函数
    list() : size(0), listEnd(nullptr), listHead(nullptr) {}

    // assign 函数用于从数组范围初始化链表
    void assign(T* begin, T* end) {
        while (begin != end) {
            Node<T>* node = new Node<T>(*begin);
            if (size == 0) {
                listHead = node;
                listEnd = node;
            } else {
                node->next = listHead;
                listHead = node;
            }
            size++;
            begin++;
        }
    }

    // 迭代器类
    class iterator {
    public:
        // 无参构造
        iterator() : pmove(nullptr) {}
        // 带节点指针的构造
        iterator(Node<T>* node) : pmove(node) {}

        // 重载赋值运算符
        void operator=(Node<T>* head) {
            pmove = head;
        }

        // 重载 * 运算符
        T& operator*() {
            return pmove->data;
        }

        // 重载 != 运算符,用于比较迭代器
        bool operator!=(const iterator& other) const {
            return pmove != other.pmove;
        }

        // 重载 ++ 运算符(后置)
        iterator operator++(int) {
            iterator temp = *this;
            pmove = pmove->next;
            return temp;
        }

    protected:
        Node<T>* pmove;
    };

    // 返回链表头的迭代器
    iterator begin() {
        return iterator(listHead);
    }

    // 返回链表尾后位置的迭代器
    iterator end() {
        return iterator(nullptr);  // 链表末尾后的位置为空
    }

protected:
    Node<T>* listHead;  // 链表头指针
    Node<T>* listEnd;   // 链表尾指针
    int size;           // 链表大小
};

int main() {
    int array[6] = {1, 2, 3, 4, 5, 6};
    list<int> myList;
    myList.assign(array, array + 6);

    list<int>::iterator iter;  // 创建正向迭代器
    for (iter = myList.begin(); iter != myList.end(); iter++) {
        cout << *iter << "\t";
    }

    cout << endl;
    return 0;
}

6.3输入输出迭代器

如果我们想将容器的内容直接输入到屏幕上面需要使用输出流的迭代器。STL为这种迭代器提供了ostream_iterator模板。用STL的话说,该模板是输出迭代器概念的一个模板,它也是一个适配器一个类函数。可以将一些其他接口转换为STL使用的接口。(

       适配器的作用是让一个接口能够与另一个系统兼容。在这里,STL(标准模板库)定义了一些标准的接口(如迭代器、容器、算法等),而有些外部的接口可能不符合这些标准。通过使用适配器,我们可以将这些外部接口转换成符合 STL 标准的接口,从而让 STL 可以识别并操作它们。

       举个例子,ostream_iterator 就是一个适配器,它将一个普通的输出流(比如 std::cout)与 STL 的迭代器接口连接起来,使得你可以通过标准的迭代器操作(比如用 std::copy)来将容器的内容输出到屏幕上。这种转换使得我们可以利用 STL 的标准算法,而不需要手动处理流的输出。

通过包含iterator头文件来声明这种迭代器:

#include <iterator>

ostream_iterator<int,char> out_iter (cout," ");

//out_iter迭代器现在是一个接口,让我们能够使用cout来显示信息

·引入 <iterator> 头文件,它包含了与迭代器相关的定义和功能,ostream_iterator 就是在这个文件中定义的 

ostream_iterator<int, char>

`这是一个模板类 ostream_iterator 的实例化(out_iterostream_iterator 的实例),表示一个输出流迭代器,它将 int 类型的数据写入到指定的输出流。

  • int:指定迭代器处理的数据类型是 int,意味着该迭代器用于输出 int 类型的元素到一个输出流,如 std::cout,但 char 参数是多余的,通常可以省略。
  • char:指定迭代器操作的是字符类型的流(通常是 char)。ostream_iterator 需要知道输出的字符类型,一般情况下,这个值默认是 char,用于表示字符流。
  • out_iter 是通过 ostream_iterator<int, char> 类模板创建的一个实例,实际上它就是一个 输出流迭代器。它在程序中起到的作用类似于传统的迭代器,尽管它的操作对象是流(如 std::cout),而不是一个容器。
  • 当使用 out_iter 时,可以像使用普通迭代器一样进行操作,比如通过 std::copy 来将数据写入流中——展现迭代器的行为

使用前提,重载

·流迭代器使用流提取运算符 >>,来读取流。也就是说,我们可以用自定义类型使用流迭代器,前提要重载 >>,没有这一步的话,编译器将无法识别如何将 对象 转换成 输出流 中的字符串形式

ostream& operator<<(ostream& out, student stu){
    out<<stu.name<<":"<<stu.age<<"\t";
}

1.输出流迭代器ostream_iterator(2种)

第一种:输出 数组 元素

int array[6] = {1,2,3,4,5,6};
//输出流迭代器

//创建输出流迭代器对象 ostreamObject,将数据写入到输出流中, cout,即标准输出
ostream_iterator<int> ostreamObject(cout, "\t");
copy(array, array + 6, ostreamObect);
cout<<endl;//输出换行符

解释:

ostreamObject(cout, "\t")构造输出流迭代器时,传递两个参数:

  • cout:输出流对象。这里我们指定 ostreamObject 将输出到 cout
  • "\t":这是每个元素输出后的分隔符,在输出每个 int 后,会输出一个制表符(\t),使输出更整齐。

copy(array, array + 6, ostreamObject);: 

这一行代码使用了 std::copy 算法,将数组的元素复制到 ostreamObject 中。std::copy 是标准库中的一种算法,用于将一个范围内的内容复制到另一个范围中。其中,ostreamObject作为目标迭代器,将 array 的数据复制到 ostreamObject,从而输出到控制台。

std::copy 会遍历数组中的每个元素,并将它们通过 ostreamObject 输出到 cout。由于 ostreamObject 每次都会在元素输出后加上 "\t",我们将会在屏幕上看到每个数字后面都有一个制表符

第二种:借助 链表 输出

结合 copy 函数来输出链表 myList 中的自定义类型 student

//定义自定义数据类型 student,它将被用来存储学生信息
struct student{
    string name;
    int age;
};

//声明list容器myList,用于存储student类型的对象。std::list 是一个双向链表容器,可以动态存储元素
list<student> myList;
student stu[3] = {"女孩", 14, "女生", 16, "女神", 18};
//assign方法用于将数组 stu 中的元素复制到myList中
myList.assign(stu, stu + 3);
copy(myList.begin(), myList.end(), ostream_iterator)<student>(cout, "\n");

解释:

copy(myList.begin(), myList.end(), ostream_iterator<student>(cout, "\n"));:

这里使用 copy 函数和 ostream_iterator 来输出 myList 中的所有元素。

  • myList.begin()myList.end():指定要复制的范围(从 myList 开头到结尾)。
  • ostream_iterator<student>(cout, "\n"):创建一个输出流迭代器,它将每个 student 对象输出到 cout,并在每个元素后面添加换行符 "\n"

2.输入流迭代器istream_iterator

istream_iterator:单向输入迭代器,只能从前往后读取,不支持回退或随机访问。它会从流中读取并解析输入,适用于数据逐一读取的情况。

istream_iterator<int> istreamObject(cin);//接收cin
istream_iterator<int> Error;//后面不带括号,说明输出错误

vector<int> myVector;//向量,用于存储输入的整数
while(istreamObject != Error){  // 表示流的结束
    myVector.push_back(*istreamObject)//尾插法
    istreamObject++;  // 迭代器自增,指向下一个输入,流迭代器支持自增操作,用来顺序读取数据
}
//输出
copy(myVector.begin(), myVector.end(), ostream_iterator<int>(cout, "\n"));
cout<<endl;

解释:
istream_iterator<int> istreamObject(cin);:

创建 输入流迭代器 istreamObject,绑定到 std::cinistream_iterator 是 STL 提供的标准输入迭代器,用于从标准输入流(或文件流)中逐个读取数据,并将其转换为指定的类型(这里是 int)。

  • istream_iterator<int>:表示该迭代器每次读取一个 int 类型的输入。

  • istreamObject(cin):将 istreamObject 与标准输入流 cin 绑定,迭代器将从 cin 中读取数据。

istream_iterator<int> Error;: 

Error 是一个默认构造的 istream_iterator<int>,它被称为“哨兵迭代器”或“流结束迭代器”。在 istream_iterator 中,默认构造的迭代器(即不绑定任何流)被认为是流的结束标志,常用于输入迭代器的比较操作,表示流的结束

vector<int> myVector;: 

声明 vector<int> 类型的动态数组 myVector,用于存储从标准输入读取的整数。vector 是 C++ 中的动态数组容器,支持动态调整大小,便于在不确定数据数量时使用。

myVector.push_back(*istreamObject);:

  • *istreamObject:对 istreamObject 解引用,获取当前输入值。
  • myVector.push_back(*istreamObject):将输入的整数尾插到 myVector 中。push_backvector 容器的成员函数,用于在容器末尾添加新元素。
  • vector::push_back 是动态数组 vector 中的常用操作,用于将新元素添加到容器末尾。适合在不确定元素数量时,动态调整存储空间。

*关于流迭代器的行为本质发文:C++ 输入流迭代器-接口和行为-CSDN博客 ,理解更深

七、仿函数

仿函数(也称为函数对象,Functors)是对象表现得像函数一样的概念。它通过定义一个operator()运算符来实现,使得对象可以像函数那样被调用。相比于普通函数,仿函数有一些优势,比如可以存储状态、支持多态性等。

实现:

仿函数通常通过 结构体 来实现,并在其中重载operator()运算符。这个运算符的 参数和返回值 可以根据你的需求 自由定义 。(只要某个对象重载了 operator() 运算符,就可以称它为仿函数。)

在标准库中,很多仿函数是使用模板类定义的。模板类允许仿函数在不同类型下被实例化并使用,比如 less<int>less<double>,它们的行为相同,但应用的类型不同。

仿函数的用途

  1. 自定义行为:仿函数可用于容器或算法的自定义行为,比如排序、过滤等。
  2. 存储状态:不同于普通函数,仿函数能储存状态,可以作为函数调用的上下文。
  3. 提高性能:在某些场合,仿函数可以更高效,因为它们在调用时没有函数指针的开销。

7.1仿函数实现:结合实际说明

1.首先是头文件

#include"标头.h"

//算法头文件  sort(排序)和for_each(输出)
#include<algorithm>
//仿函数头文件  greater和less(已经定义好的仿函数)
#include<functional>

2.使用

使用了仿函数 less<int>greater<int>,这些仿函数定义在标准库 <functional> 头文件中。它们本质上是内置的仿函数,用于实现标准算法(如 sortfor_each)的自定义行为。

//sort算法
//for_each(起始位置,终止位置,子进程)

template<class type>
void print(type data){
    cout<<data; //会将 data 输出到标准输出流。
}
int main(){
    int arrary[3] = {1, 3, 2};

    sort(array, arrary + 3, less<int>());//升序
// 使用for_each算法遍历输出排序后的数组内容
    for_each(array, array + 3, print<int>);

    sort((array, array + 3, greater<int>());//降序
// 使用copy算法将排序后的数组内容复制到标准输出流
    copy(array, array + 3, ostream iterator<int>(cout));

    return 0;
}

输出:

123
321

解释:

·class type 中的 class 并不限制为类类型,它可以表示任意类型(包括内置类型和自定义类型)。通过模板参数 type,可以让函数适配不同类型的数据,使其更具通用性。

·print<int> 是一个普通的模板函数。它被传递给 for_each 以对数组的每个元素进行输出操作。

·copyarray 中的元素复制到输出流 coutostream_iterator 输出迭代器将元素直接输出到控制台。

less<int>——仿函数的实现:

在xsddef查看,发现less实际上是个类(还是一个模板类),我们在运用时一直是less<int>(),根本原因是类中重载了operator()括号运算符:

*简单介绍"XSD":

 "XML Schema Definition" ,用于定义 XML 文档的结构、数据类型和约束。XSD 允许你指定 XML 文件中元素、属性及其关联的数据类型,是对 XML 结构和数据的一种详细描述和验证方式。它比传统的 DTD(文档类型定义)更强大,能够提供更精细的控制。

在实际使用中,XSD 用于规范化 XML 文件的结构,确保数据符合预定的格式和规则。通过 XSD,可以验证 XML 文件是否符合指定的结构要求,例如检查某些元素是否存在,或者数据是否符合特定格式(如电子邮件地址、日期等)

·less 定义在 <functional> 头文件中(less<functional> 头文件中提供了实现),是一个模板类(允许我们在排序等场景中自定义比较方式。,意味着它可以针对不同的数据类型实例化。例如,less<int>less 类的 int 类型实例,而 less<double> 则是 lessdouble 类型实例。

· less<int>() ,实际上是在创建 less 模板类的 int 实例,并在 sort 中使用它。

·less 模板类定义了 operator(),而每个实例化(如 less<int>)通过继承模板类的定义,使得函数对象(仿函数)也具备了 operator()operator()用于对两个元素进行比较,这个运算符的返回值是 truefalse,因此在 sort 排序中可以用它来判断排序顺序。

*说明:类的实例化会继承类的所有成员和行为,包括普通成员函数和重载的 operator(),“继承”是指指每个实例化的对象都具备该类所定义的成员和行为

less模板类的简化实现示例:

template <typename T>
struct less {
    bool operator()(const T& a, const T& b) const {
        return a < b;
    }
};

其中,less 是一个模板类,operator() 定义了小于比较的逻辑。因此 less<int>() 实际上是生成了一个具有int类型的比较函数对象(仿函数),它的作用是将 a < b 的结果作为判断依据。

3.使用自定义类型输出

标头.h文件:

#include<iostream>
#include<string>
using namespace std;

struct student{
    string name;
    int age;
};
ostream& operator<<(ostream& out, student object){
    out<<object.name<<":"<<object.age<<endl;
    return out;
}

main.cpp文件:

template<class type>
void print(type data){
    cout<<data;
}
int main(){
    student stu[3] = {"name1", 14, "name2", 16, "name3", 18};
    for_each(stu, stu + 3, print<student>);    

    return 0;
}

输出:

name1:14
name2:16
name3:18

4.使用自定义类型排序(name)

由于less类重载了operator()括号运算符,因此我们也来这样做:

也是第一种方式(按照xsddef来写)

标头.h:

#include<iostream>
#include<string>
using namespace std;

struct student{
    string name;
    int age;
    bool operator<(student object){
        return this->name < object.name;
    }
};
ostream& operator<<(ostream& out, student object){
    out<<object.name<<":"<<object.age<<endl;
    return out;
}

template<class type>
class myLess{
    bool operator()(type object1, type object2){
        return object1 < object2;
    }
}

main.cpp文件:

template<class type>
void print(type data){
    cout<<data;
}
int main(){
    student stu[3] = {"name1", 14, "name2", 16, "name3", 18};
    sort(stu, stu + 3, myLess<student>());  

    return 0;
}

输出:

name1:14
name2:16
name3:18
4.1问题:使用自定义类型排序(name)

若在成员函数中,重载了两个小于运算符,如下,使用时到底调用哪一个(有问题):

struct student{
    string name;
    int age;
    bool operator<(student object){
        return this->name < object.name;
    }
    bool operator<(student object){
        return this->age < object.age;
    }
};

因此一般不在成员函数中写——而是在该文件中直接单独建一个函数(不在某类或某成员函数中):

也是第二种方式写(单独建函数)

//升序
bool sortByAgeMax<(student object1, student object2){
     return object1.age < object2.age;
}
//降序
bool sortByAgeMin<(student object1, student object2){
     return object1.age > object2.age;
}

*仍然要在成员函数中声明:

struct student{
    string name;
    int age;
    bool operator<(student object){
        return this->name < object.name;
    }
    bool operator>(student object){  
        return this->age > object.age;
    }
};

*对于age只声明一个成员函数 operator> 也能实现最终结果:

  • 成员函数重载运算符:在 student 类中定义了 operator>,它可以通过比较两个对象的 age 来实现对象之间的比较。这种方式是面向对象的实现,它让每个 student 对象知道如何与其他 student 对象进行比较。因此,只需要实现一个运算符(例如 operator>),就能在排序时直接利用比较规则(如 std::sort)。

  • 内部逻辑:在你定义 operator> 的时候,这个运算符隐式地理解了如何比较 student 对象的 age,而不需要显式地传递比较规则。排序函数(如 std::sort)会自动调用你定义的比较函数(operator>)。

  • std::sort 中,显式地传递了比较函数(sortByAgeMaxsortByAgeMin),这意味着每次排序时需要手动指定比较规则。

*注:

std::sort 中,如果没有提供自定义比较函数,它会默认使用 < 运算符进行升序排列 ,所以没有写age的

输出:

//升序
name1:14
name2:16
name3:18
//降序
name2:16
name3:18
name1:14

5.仿函数应用

写一个类mySum、重载一个括号、用在for_each里面就可以输出了

#include"标头.h"
#include<algorithm>
#include<functional>

//通过引用参数修改传入的整数值并将其输出
void print(int& num){
    num += 10;
    cout<<num<<"\t";
}
class mySun{
public:
    mySum():sum(0){}
    void operator()(int num){
        sum += num;
    }
    void print(){
        cout<<"和为"<<sum<<endl;
    }
protected:
    int sum;
};
int main(){
    int array[3] = {30, 60, 90};    
    for_each(array, array + 3, print);
    mySum mysum = for_each(array, array + 3, mySun());
    mysum.print();
    return 0;
}

输出:

40    70    100    和为:210

解释:

mySum 类:

  • mySum 类实现了 operator(),这使得它变成了一个仿函数。仿函数是重载了函数调用运算符的类对象,可以像普通函数一样被调用。在 for_each 中,创建了 mySum() 的一个实例,并将其作为操作元素的函数对象传入。
  • operator()mySum 类中的作用是将传入的数值累加到 sum 成员变量中,因此每次调用 operator() 时,sum 会增加。
  • mySum 是一个普通的类,不需要模板类,因为它只需要处理特定类型(整数)。
  • 标准库中的许多仿函数(如 std::less, std::greater)是模板类,因为它们需要处理不同类型的数据,通过模板类实现了通用性和灵活性。
  • 使用模板类可以让仿函数更具通用性和可复用性,适应不同的数据类型。

for_each(array, array + 3, print);:

是一种使用算法的方式,用于遍历一个数组并对其中的每个元素应用指定的操作。

  • array:这是一个数组的起始地址(指向数组第一个元素)。
  • array + 3:这是一个指向数组第三个元素之后位置的指针,即指向数组末尾的“下一个位置”。因此,arrayarray + 3 表示的是数组中前 3 个元素的范围。
  • print:这是一个函数(或函数对象),在 for_each 函数中作为操作符传递给每个元素。它会被应用到数组中的每一个元素上

原文地址:https://blog.csdn.net/Tsmall_/article/details/143494025

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!