C++学习笔记_14 迭代器、与容器无关的算法函数 2021-05-12

简介: C++学习笔记_14 迭代器、与容器无关的算法函数 2021-05-12
// C++学习笔记_14 迭代器、与容器无关的算法函数
#include "stdafx.h"
#include<iostream>
#include<string>
#include"List.h" //这是一个单向链表类 
#include"DbList.h" //这是一个双链表类 
using namespace std;
void TestString()
{
    string ss = "Hello world!";
    string::iterator it; //定义一个迭代器变量
                         //迭代器:相当于一个指针,指向 ss 内部的字符
    cout << "ss:" << ss << endl;
    for (it = ss.begin(); it != ss.end(); it++) {
        // it = ss.begin():先让 it 指向 ss 的第一个字符,
        // cout << *it    :输出这个字符
        // it++           :让 it 指向下一个字符。 直到末尾位置 循环结束
        cout << *it << endl;
    }
    return;
}
template<typename T>
void PrintList_0(CList<T>  &tList)
{
    for (int i = 0; i < tList.size(); i++){
        cout << tList.getValue(i) << ",";
    }
    cout << "\b " << endl; // \b 退格,去掉末尾的逗号
}
template<typename T>
void PrintList_0(CDbList<T>  &tList)
{
    for (int i = 0; i < tList.size(); i++){
        cout << tList.getValue(i) << ",";
    }
    cout << "\b " << endl;
}
//两个函数,除了入参类型不一样,代码实现都是一样的
//---》合并成一个 模板函数
//这个 T 可以是 CList<int>, CDbList<int> , CList<string>, .....
template<typename T>
void PrintList_1(T  &tList)
{
    for (int i = 0; i < tList.size(); i++){
        cout << tList.getValue(i) << ",";
    }
    cout << "\b " << endl; // \b 退格,去掉末尾的逗号
}
//如果,我们想对 list 中存字符串的话,每个字符串换行输出,存整数或其他的还是不变
//----》偏特化
//--> 我们需要把  CList,CDbList ---》用一个模板来替代
//--> 把里面存的东西 int ,string, ... --> 也用一个模板来替代
//模板嵌套:第一个模板参数 CL, 第二个模板参数 T
// template <typename T0> class CL 表示 CL 这个模板,本身就是一个模板类
// ---》template <typename T0> 用于声明 CL 是一个模板类,包含 1 个模板参数 T0
// ---》和 函数声明很类似,这里 T0 不起作用,可以省略
template<template <typename T0> class CL, typename T>
void PrintList(CL<T>  &tList)
{
    for (int i = 0; i < tList.size(); i++){
        cout << tList.getValue(i) << ",";
    }
    cout << "\b " << endl; // \b 退格,去掉末尾的逗号
}
//对 string 做特化
template<template <typename T0> class CL>
void PrintList(CL<string> &tList)
{
    for (int i = 0; i < tList.size(); i++){
        cout << tList.getValue(i) << endl;
    }
    cout << "\b " << endl; // \b 退格,去掉末尾的逗号
}
//观察 PrintList 函数: for 循环,依次 getValue
//每次 getValue 都需要从 pHead 重新往后找
//--》我们使用这个函数输出链表元素,有个问题:
//效率低:输出第 M 个元素,需要从 pHead 后移 M 次
//        总共输出 N 条数据,需要查找次数为 1+2+3+...+N = N*(N-1)/2
//CList, CDbList 都需要实现getValue 函数名,入参等必须要一样,否则不能写成PrintList模板函数
//===> 解决方式: 迭代器
//     用一个指针指向元素,++ 操作自动移动到下一个元素
// --》自己来实现一个迭代器
//实现方式
//迭代器,其实是容器内部的一个类。这个类中定义一个指针指向节点的值
//       他重载 ++ 操作,让这个类的对象指向下一个节点的值
//迭代器隐藏了数据的存储方式
//使用迭代器,可以实现算法函数,可以编写与类型无关的函数
void TestList()
{
    CList<int>    iList;
    CDbList<int>  dList;
    CList<string>    sList;
    int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8 };
    for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++){
        iList.insert(i, arr[i]);
        dList.insert(i, arr[i]);
        sList.insert(i, string(5, 'A' + i));
    }
    //接下来我们做输出
    iList.Print();
    dList.Print();
    //如果我们想以逗号隔开?
    //或者说 每行输出 4 个,每 4 个换一行输出?
    //等等
    //每个人都想按照自己格式输出,一般我们写一个链表,不会实现 输出的操作(调试才写这个)
    //--》通过 getValue 来输出
    cout << "iList:";
    //PrintList_1(iList);
    PrintList(iList);
    cout << "dList:";
    //PrintList_1(dList);
    PrintList(dList);
    cout << "sList:";
    PrintList(sList);
}
//使用自定义的迭代器:
void TestIterator()
{
    CList<int> iList;
    CList<int>::iterator it;
    int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8 };
    for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++){
        iList.insert(i, arr[i]);
    }
    for (it = iList.begin(); it != iList.end(); it++){
        cout << *it << ",";
    }
    cout << endl;
}
int _tmain(int argc, _TCHAR* argv[])
{
    //TestString();
    //TestList();
    TestIterator();
    system("pause");
  return 0;
}
//作业:实现 CDbList 的迭代器
//List.h
//#pragma once 这是微软的,不是通用的
#ifndef __LIST_H__
#define __LIST_H__
#include"stdafx.h"
#include"stdlib.h"
//C 语言链表两个问题:
//1: 定义链表的时候,我们需要指定链表中存放的数据类型
//   不同数据类型的链表,需要 分别实现
//2: LIST *pNode;  语义容易引起误解
//   它既可以代表一个节点,也可以代表一个链表
//--》1:通过模板类来实现。2:通过封装Node来规避歧义
//这里:头结点 不放数据,仅仅表示这是链表的引子
template<typename T>
class CList
{
private:
    class Node
    {
    public:
        T data;
        Node *pNext;
        Node(T t_data) :data(t_data), pNext(NULL){}
    };
    Node *pHead;  //这是链表的头结点
    size_t iSize; //链表的长度(节点个数)
public:
    //T() : 生成一个 T 的默认值(比如 int 生成一个0, string 生成一个空串,等等)
    CList() :pHead(new Node(T())), iSize(0){}
    ~CList()//释放整个链表 申请的内存
    {
        clear();delete pHead;
    }
    size_t size(){ return iSize; } //获取链表长度
    //增删改查
    int insert(size_t pos, T t_data);
    int erase(size_t pos);
    int modify(size_t pos, T t_data);
    T& getValue(size_t pos);
    void clear(); //清空节点,保留头结点
    void reverse(); // 链表逆序
    void Print() //遍历链表数据
    {
        Node *pNode = pHead->pNext;
        while (pNode){
            cout << pNode->data << " ";
            pNode = pNode->pNext;
        }
        cout << endl;
    }
    ///
public:
    class iterator
    {
        friend class CList;
    private:
        Node *pNode;  //指向某个节点的指针
        //构造为什么要定义成private?
        //-->做隐藏:外部不可见 ( pNode 在外部是不能用的)
        iterator(Node *pTmp) :pNode(pTmp){}
    public:        
        //重载 ++,--,==, !=, * 等运算符 (这里单向链表没有--)
        iterator(){}
        // it++  和 ++it 怎么区分 ?
        iterator operator ++ () // ++it (前++)
        {
            pNode = pNode->pNext; //节点后移
            return *this;  //返回自己
        }
        iterator operator ++ (int) //增加一个哑元 --》后++
                   //理解方式:这里 有个int,表示 ++ 连接两个变量
                   //          () 内的东西作为第二个变量
                   //这里理解为   it ++ (哑元) --》 就是 it++
        {
            iterator itTmp = *this;
            pNode = pNode->pNext;
            return itTmp;
        }
        //判断两个迭代器是否指向同一个节点
        bool operator == (const iterator &it){return pNode == it.pNode;}
        bool operator != (const iterator &it){return pNode != it.pNode;}
        //重载 *  --> 取节点内存储的值!!! , 返回引用 (可以通过它修改元素)
        T& operator * (){return pNode->data;}
        //要不要重载 "->" ?
        // it->xxx 会被编译器解释 为 (*it).xxx
    };
public:
    //begin 和 end 是 CList的成员函数,不是 iterator的
    iterator begin()
    {
        //返回iterator对象,这个对象的 pNode 指向CList的第一个节点
        //iterator(Node*) 是iterator的私有构造函数,外部类 CList 不能调用
        //-->解决方法: 把 CList 声明成 iterator 友元类
        return iterator(pHead->pNext);
    }
    iterator end() {return iterator(NULL);}
};
//模板类成员函数在外部实现,需要加上模板声明
template<typename T>
int CList<T>::insert(size_t pos, T data)
{
    //在 pos 位置前插入节点,节点数据是 data
    //  pos 从 0 开始编号
    if (pos > iSize) {return -1; }//越界
    Node *pNewNode = new Node(data);
    //找插入位置前面那个节点
    size_t i = 0;
    Node *pNode = pHead; //起始节点
    while (i < pos){pNode = pNode->pNext;i++;}
    //把 pNewNode 插入在 pNode 的后面
    //1:pNewNode 的 pNext 赋值, 指向 pNode 的下一个元素
    pNewNode->pNext = pNode->pNext;
    //2:把 pNode 的 pNext 指向 pNewNode
    pNode->pNext = pNewNode;
    iSize++;
    return 0;
}
template<typename T>
int CList<T>::erase(size_t pos)
{
    if (pos > iSize - 1){ return -1; }//越界,节点不存在
    //查找待删除节点的上一个元素、
    size_t i = 0;
    Node *pNode = pHead; //起始节点
    while (i < pos){pNode = pNode->pNext;i++;}
    Node *pDelNode = pNode->pNext; //待删除节点
    //删除元素,只需要把 pNode 指向 pDelNode 的下一个节点就可以了
    pNode->pNext = pDelNode->pNext;
    delete pDelNode;//释放节点空间
    iSize--;
    return 0;
}
template<typename T>
int CList<T>::modify(size_t pos, T data)
{
    if (pos > iSize - 1){return -1;}//越界,节点不存在
    //这个时候跟上面有点不一样:不再找上一个节点,直接找需要修改的节点
    int i = 0;
    Node *pNode = pHead->pNext; //从第 0 个节点开始找
    while (i < pos){pNode = pNode->pNext;i++;}
    pNode->data = data;
    return 0;
}
template <typename T>
T& CList<T>::getValue(size_t pos)
{
    //越界判断
    if (pos > iSize - 1){
        //return -1;//这个显然不行,类型和返回值类型不匹配
           //事实上这里返回什么都不合适
        //1:写 assert  --》 比较耗资源,一般在调试的时候使用
        //2:抛出异常   --》 C++比较正常的解决方法 throw out_of_range;
        //3:不做任何处理 --》C 语言的一般处理方式
        //              我们已经对外提供了 size() 这个接口
        //              使用者完全可以判断是否越界
    }
    //这个时候跟上面有点不一样:不再找上一个节点,直接找需要修改的节点
    int i = 0;
    Node *pNode = pHead->pNext; //从第 0 个节点开始找
    while (i < pos){pNode = pNode->pNext; i++;}
    return pNode->data;
}
template <typename T>
void CList<T>::clear()
{
    //释放所有节点 (头结点除外)
    if (iSize == 0){return;}
    Node *pNode = pHead->pNext;
    while (pNode){
        //把pNode脱链, pHead 的pNext 指向 pNode 的下一个节点
        //1:把下一个待删除节点保存到 pHead->pNext 中
        pHead->pNext = pNode->pNext;
        //2:删除节点
        delete pNode;
        //进入下一个循环,待删除节点后移
        pNode = pHead->pNext;
    }
    iSize = 0;
    return;
}
template<typename T>
void CList<T>::reverse()
{
    //如果 链表节点个数小于两个,那么逆序结果不变
    if (iSize < 2){ return; }
    Node *pNode = pHead->pNext;  //待翻转节点(第0个节点)
    Node *pTmpNode = pNode->pNext; //下一个待翻转节点(第一个节点)
    //第 0 个节点的 pNext 指向NULL, 指向前,先保存它的下一个节点
    pNode->pNext = NULL;
    pNode = pTmpNode;
    //从第一个节点开始到最后一个节点,依次把 pNext 指向前面那个节点
    while (pNode)
    {
        pTmpNode = pNode->pNext; //保存下一个待翻转节点(后节点)
        //翻转节点
        pNode->pNext = pHead->pNext;
        //重置变量,进入下一轮循环
        pHead->pNext = pNode; //下一个待翻转节点指向的目标节点(前节点)
        pNode = pTmpNode;        //下一个待翻转节点(中节点)        
    }    
    //pHead 的 pNext 指向最后一个节点
    //--> 在循环体内已经解决了
    return;
}
//C 语言方式实现链表:单链表 -- 保存整数
//我们写链表有两种方式
// 第一种:头结点不放数据,仅仅指明 这是链表的引子
// 第二种:头结点放数据,和正常节点一样
// 这里我们写第二种情况:头结点放数据。
//节点的结构体
typedef struct tagList
{
    int data;                   //数据部分
    struct tagList *pNext;      //指向下一个节点的指针
}LIST;
//增删改查
//初始化: 传入的是 已经存在 的 LIST 对象的地址
int Init_List(LIST **ppList)
{
    *ppList = (LIST*)malloc(sizeof(LIST));
    (*ppList)->data = 0;
    (*ppList)->pNext = NULL;
    return 0;
}
//在 pHead 这个链表中,第 pos 个节点位置,插入节点,节点数据是 data
int Insert_List(LIST **ppHead, int pos, int data)
{
    LIST *pPos = *ppHead;
    int i = 0;
    //如果 pos == 0, 新插入节点做为头结点
    if (pos == 0)
    {
        LIST *pNode = (LIST*)malloc(sizeof(LIST));
        if (pNode == NULL)
        {
            return -1;
        }
        pNode->data = data;
        pNode->pNext = *ppHead;
        *ppHead = pNode;  //新的头结点
        return 0;
    }
    //查找节点的时候,需要考虑是否越界
    //      越界解决:1- 插入失败,2- 插入末尾
    //还需要考虑插入位置,插入 pos 节点前 --》找 pos 前面那个节点
    while (i < pos - 1)
    {
        if (pPos == NULL)
        {
            //越界 
            return -1;
        }
        pPos = pPos->pNext;
        i++;
    }
    LIST *pNode = (LIST*)malloc(sizeof(LIST));
    if (pNode == NULL){return -1;}
    pNode->data = data;
    //新增节点插入 pPos 后面
    pNode->pNext = pPos->pNext; //把 pPos 的下一个节点 作为 pNode 的下一个节点
    pPos->pNext = pNode;   // pPos 的下一个节点指向 pNode
    //这两行不能调换
    return 0;
}
//销毁链表:释放内存
void Destroy_List(LIST **ppHead)
{
}
#endif
相关文章
|
8天前
|
设计模式 存储 Android开发
c++的学习之路:18、容器适配器与反向迭代器
c++的学习之路:18、容器适配器与反向迭代器
18 0
|
5天前
|
C++
C++虚函数学习笔记
C++虚函数学习笔记
9 0
|
6天前
|
存储 算法 程序员
C++从入门到精通:2.2.1标准库与STL容器算法深度解析
C++从入门到精通:2.2.1标准库与STL容器算法深度解析
|
14天前
|
机器学习/深度学习 存储 算法
基础算法学习笔记(C++)
基础算法学习笔记(C++)
54 0
|
14天前
|
存储 C++ 索引
C++的STL学习笔记
C++的STL学习笔记
50 0
|
14天前
|
C++ 容器
约瑟夫经典问题C++,STL容器queue解法
约瑟夫经典问题C++,STL容器queue解法
12 0
|
22天前
|
容器
C++map/multimap容器
C++map/multimap容器
|
22天前
|
C++ 容器
C++入门到理解set/multiset容器、pair对组
C++入门到理解set/multiset容器、pair对组
C++入门到理解set/multiset容器、pair对组
|
29天前
|
存储 C语言 C++
C++中STL常用容器(vector、deque、list、map、set)一文带你了解
C++中STL常用容器(vector、deque、list、map、set)一文带你了解
|
29天前
|
存储 算法 C++
C++容器STL相关面试问题
C++容器STL相关面试问题