字符串—迭代器—友元

01 字符串类型

在C语言中,是没有字符串类型的,C里面使用字符数组、字符指针表示一个字符串

        char str[128] = {"hello"};

        const char *str = "nihao!";


在C++标准库中,提供了一个字符串类型,实际上是一个字符串类
类名叫做 string,专门用来描述字符串对象的,提供一些常用的字符串操作接口

使用步骤:

1. 包含头文件

        #include <string>

        iostream间接包含了string,有 #include <iostream> 可以不加 #include <string>

2. 指定作用域

        std::string

        using std::string;

        using namespace std;

3. 实例化字符串对象(构造函数)

        cppreference  // 百度查看文档

std::string s1; // 实例化一个空的字符串

std::string s2("hello"); // 实例化对象,同时初始化
std::string s2{"hello"}; // 实例化对象,同时初始化
std::string s2 = "hello"; // 实例化对象,同时初始化

std::string s4 = s2; // 用一个已有对象初始化新对象

std::string{"hello"}; // 临时的匿名对象

std::string s4{5, 'a'}; // "aaaaa"

4. 常用的成员函数

a. 元素访问

(1) at  -----> 访问指定字符,有边界检查

函数解析:
    返回位于指定位置 pos 的字符的引用,从下标0开始
    进行边界检查,非法访问时抛出 std::out_of_range 类型的异常

参数:
    pos --- 要返回的字符位置

返回值:
    返回位于指定位置 pos 的字符的引用,从下标0开始
    
异常:
    在 pos >= size() 时抛出std::out_of_range
    如果因为任何原因抛出了异常,那么此函数无效果

复杂度:
    常数

例子:
    std::string s("message"); // 为容量
    
    s = "abc";
    s.at(2) = 'x'; // ok
    std::cout << s << '\n'; // abx
 
    std::cout << "string size = " << s.size() << '\n'; // 3
    std::cout << "string capacity = " << s.capacity() << '\n'; // 7
 
    try {
        // 抛出,即使容量允许访问元素
        s.at(3) = 'x';
    } catch (std::out_of_range const &err) {
        std::cout << err.what() << '\n';
    }

(2) operator[] -----> 访问指定字符([]是运算符重载:让string类型可以通过中括号访问字符)

返回引用,不进行边界检查
如果 pos >= size(),那么行为未定义    

例子:
    cout << s[10] << endl;
    s[10] = 'A';

(3) front -----> 访问首字符

函数解析:
   返回字符串中首字符的引用。如果 empty() == true,那么行为未定义

参数:
    无

返回值:
    返回字符串中首字符的引用,等价于 operator[](0)

复杂度:
    常数

例子:
     s.front() = 'A'; 
    cout << s.front() << endl; // <===> s[0]

(4) back -----> 访问最后的字符

函数解析:
   返回字符串中末字符的引用。如果 empty() == true,那么行为未定义

参数:
    无

返回值:
    返回字符串中末字符的引用,等价于 operator[](size() - 1)

复杂度:
    常数

例子:
    std::string s("Exemplary");
    char &back = s.back();
    back = 's';
    std::cout << s << '\n'; // "Exemplars"

    s.back() = 'A'; 
    cout << s.back() << endl; // 'A'

(5) data -----> 返回指向字符串首字符的指针

(6) c_str -----> 返回字符串不可修改的 C 字符数组版本

string name = "/dev/fb0";
open(name, O_RDWR); // ERROR
====>
open(name.c_str(), O_RDWR);
b. 迭代器(拥有指针类似的行为)

        是一种思想,是为了让用户更加方便的遍历容器中的元素而实现的接口

        可以让用户在不需要了解容器内部结构的情况下,很方便的顺序访问容器中的元素

        // 迭代器可以看做是表示容器中的用户数据某一个位置的"指针对象"
        字符串提供了一些接口,可以获取“迭代器”

begin        返回指向容器第一个元素的迭代器(位置)
end        返回指向容器最后一个元素后面的迭代器(位置)

        正向迭代器:++操作的时候是往后移动的

c:const 的意思
cbegin:返回指向容器第一个元素的常量迭代器(位置)
cend:返回指向容器最后一个元素后面的常量迭代器(位置)
        不能通过迭代器去修改迭代器指向的位置的数据,只能访问

r:reserve 反向的意思
rbegin:返回指向容器最后一个元素的反向迭代器(位置)
rend:返回指向容器第一个元素前面的反向迭代器(位置)
        ++操作的时候是往前移动的

crbegin        返回指向容器最后一个元素的常量反向迭代器(位置)

crend        返回指向容器第一个元素前面的常量反向迭代器(位置)

自己实现的容器,一般需要自己提供迭代器接口的,如

c. 容量
empty    检查字符串是否为空,即是否 begin() == end()     <======

size    
length    返回字符串中的字符数,不包括末尾的'\0'    <======

max_size    返回字符数的最大值(系统能够允许的最大数量)

reserve    保留存储,字符串有一个属性叫做容量
           可以使用这个接口设置容量
    
capacity    返回当前对象分配的存储空间能保存的字符数量

shrink_to_fit    通过释放不使用内存减少内存使用


d. 操作
clear    清除内容    <=====
intsert    插入字符 / 指定的字符 / 字符串中的子串...    <======

    std::string s = "Hello";

(1) // insert(size_type index, size_type count, char ch);
    s.insert(0, 1, 'E'); // 在位置0插入1个'E'
    // "EHello"

(2) // insert(size_type index, char const* s);
    s.insert(6, "C++"); // 在位置6插入字符串"C++"
    // "EHelloC++"

(3) // insert(size_type index, string const& str, size_type index_str, 
                                                        // size_type count);
    // 在位置9插入另一个字符串的子串
    s.insert(9, "is an example string.", 0, 13);
    // "EHelloC++is an example"
erase    移除字符    <=====
    从 string 移除指定的字符
        1) 
    



 

02 迭代器

设计模式是软件开发人员经过长期的实验和总结出来的,可以解决特定问题的一套解决方案

经典的设计模式有23个,每一个设计模式描述了一个在我们周围不断发生的问题,以及这个问题的解决方案


迭代器模式:

        在软件开发过程中,集合内部的结构经常发生变化,对于这些集合对象

        (用户其实不关心集合是如何实现的,只要能够正常使用即可)

        我们希望在可以不了解(不暴露)内部结构的同时,可以让外界透明的访问其中的元素

        (用户可以通过集合提供的接口,方便的访问元素)

         不管集合内部结构如何变化,,反正对于集合外部的访问接口都保持不变

        这种"让外界透明的访问"为同一种接口(begin/end),可以在多种集合对象上面进行同一种操作
        使用面向对象的技术,将这种遍历机制抽象为"迭代器对象",为遍历"变化中的集合对象"提供一种不变的访问接口


<设计模式>中对迭代器模式的定义如下:

        提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露该对象的内部表示!!!

        

C++的一般做法:

        把遍历集合使用的"指针",封装成一个迭代器类型,并且迭代器类型向外提供相应的接口(++、+=、*....),并且对应的集合对象也应该向外提供获取迭代器的接口(begin/end)

class Iterator {
    private:
        // 指向元素类型的指针,假设为类型T
        T *m_t;
    public:
        // 构造函数 
        // 析构函数 
        ....
        next(); // 让迭代器指向当前条目的下一个条目------>++
        +=
        ==
        !=
        *
        ...
};

把迭代器完成之后,容器对象应该向外提供获取迭代器的接口
    Iterator begin();
    Iterator end();

        

迭代器对象一般是能够自由的访问容器对象的所有私有成员的!!!


一个对象如何去访问另一个对象的私有成员呢?

        =====>

        友元

03 友元

在某些情况下,允许特定的非成员函数访问一个类的私有成员,同时仍然阻止一般的访问,这种操作可以很方便的实现
        利用友元,友元是对类封装机制的一种补充


如果一个函数或者类(A)与另一个类(B)存在这种友元关系(B声明A是它的朋友)
那么这个函数或者类(A)就可以访问另一个类(B)的私有成员和保护成员


友元提高了程序的运行效率,但是破坏了类的封装性和隐藏性
使得非成员函数或者其他类的成员函数可以访问类的私有成员


友元分为两种:

        友元函数

                a. 普通的全局函数作为类的友元函数

                        友元函数是某些不是类的成员函数却能够访问类所有的成员的函数
                        类授予它特别的访问权限,这样该友元函数就能够访问类中的所有成员

                        友元声明:

                                在授权类中声明访问权限

                                friend 返回值类型 函数名(参数类型列表);

         
      b. 类的成员函数作为另一个类的友元函数

                         友元声明:

                                在授权类中声明

                                friend 返回值类型 类名::函数名(参数类型列表);

                        类(Boy)中的成员函数成为了类(Girl)中的友元函数,这样类Boy中的该成员函数就可以访问类Girl中的所有成员(包括私有和保护)

                        当用到友元成员函数时,需要特别注意友元声明和友元定义之间的相互依赖关系,在该例子中:类Boy必须先定义,类Girl需要在前面声明,并且作为友元的成员函数必须在最后实现

                         更一般的讲,必须先定义包含成员函数的类Boy,才能将该成员函数设置为其他类(Girl)的友元函数,而且这个函数必须在两个类的后面定义!!!

        

        友元类

                 如果类A是类B的朋友(B把A声明为朋友),那么类A的所有成员函数都可以访问类B的私有成员

                 友元类声明:

                        friend class 类名;
 

注意:

1. 因为友元函数不是授权类的成员,所以它不受所在类声明区域(private/public/protected)的影响

        但是我们通常会把所有的友元声明组织到一起,放到类的开始

        甚至可以在友元函数声明处直接定义该函数,但这种函数不是成员函数,不建议这么写,因为不具有可读性

2. 友元声明是以friend关键字开始,它只能出现在类的定义中,因为它的目的是声明某一个类或者函数是它的朋友,定义处不加 friend 关键字

3. 友元关系是单向的

4. 友元关系是不可以传递的,类A把类B当做朋友,类B把类C当做朋友,类A和类C不一定是朋友​​​​​​​

应用友元:

        1. 链表类和节点类
                链表类中的成员函数希望能够直接访问节点类的私有成员的

                在节点类中应该声明链表类是它的朋友,在链表类中就可以直接访问节点类的私有数据

        2. 迭代器类和容器类
                 迭代器类是为容器类服务的
                 希望在迭代器中能直接访问容器的一些隐藏成员
                 容器类应该把迭代器类声明为友元

#include <iostream>
#include <string>

using namespace std;

class B;

class A {
    friend void PrintA(A &a);
    private:
        string m_nameA;
        int m_ageA;
    public:
        A(const char *name = "xiaoming", int age = 18) : m_nameA(name), m_ageA(age) {}

        void showA() {
            cout << "showA : " << m_nameA << " " << m_ageA << endl;
        }

        void OptB(B &b);
};

void PrintA(A &a) {
    a.m_nameA = "laowang";
    a.m_ageA = 20;
}

class B {
    friend void A::OptB(B &b);

    private:
        string m_nameB;
        int m_ageB;
    public:
        B(const char *name = "hhhhh", int age = 11) : m_nameB(name), m_ageB(age) {}

        void showB() {
            cout << "showB : " << m_nameB << " " << m_ageB << endl;
        }        
};

void A::OptB(B &b) {
    b.m_nameB = "lw";
    b.m_ageB = 100;
}

int main() {

    A a;
    a.showA();

    PrintA(a);
    a.showA();

    B b;
    b.showB();

    a.OptB(b);
    b.showB();

    return 0;
}

04 练习

1. 自己写一个MyString类

2. 使用学过的内容实现一个C++电子相册,在面向对象的思想中,电子相册是一个类型

class 电子相册 {
    private:
        Screen s;
        Dir d;
        输入设备
    public:
        run();
};

电子相册 x;
x.run();

3. 写一个单链表类(std::forward_list),属性和行为自己考虑
MyForward_list.hpp

#ifndef __MYFORWARD_LIST_HPP__
#define __MYFORWARD_LIST_HPP__

#include <iostream>
#include <string>

using namespace std;

using Type = string; // Type是string的别名

// 节点类
class Node {
    // 节点把链表当成朋友,链表的所有成员函数都能够访问节点的私有成员
    // friend class MyForward_list; // 声明链表类为节点类的友元类
    // 如果不使用友元,节点类就需要提供接口
    private:
        Type m_data; // 节点拥有的用户数据
        Node *m_next; // 指向下一个节点的指针
    public:
        // 构造函数
        Node(Type data);
        
        // 析构函数
        ~Node();

        // 获取用户数据
        Type getData() const; // 获取数据
        Node* getNext() const; // 获取当前节点下一个节点的地址
        // 设置节点数据
        void setData(Type data);
        // 设置当前节点下一个节点的地址
        void setNext(Node *next);
};

// 定义链表类
class MyForward_list {
    private:
        int m_size = 0; // 链表中当前的节点个数
        Node *m_first = nullptr; // 指向链表第一个节点的指针
        Node *m_last = nullptr; // 指向链表最后一个节点的指针
    public:
        // 构造函数
        MyForward_list();

        // 析构函数
        ~MyForward_list();

        // 拷贝构造函数
        MyForward_list(const MyForward_list &list);

        // 清空链表(删除所有的数据节点)
        void clear();

        // 添加元素
        void push_back(Type data);
        void push_front(Type data);

        // 删除元素(删除第一个节点,并且返回数据)
        Type pop_front();

        // 获取元素个数
        int size() const;

        // 判断链表是否为空
        bool isEmpty() const;

        // 查找元素(返回查找到的节点的地址,如果没找到返回nullptr)
        Node* find_value(Type data);

        // 打印元素
        void printList() const;
};

#endif

MyForward_list.cpp

#include "MyForward_list.hpp"

// 构造函数
Node::Node(Type data) : m_data(data), m_next(nullptr) {
    cout << "Node constructor" << endl;
}

// 析构函数
Node::~Node() {
    cout << "Node de_structor" << endl;
}

// 获取用户数据
Type Node::getData() const { // 获取数据
    return m_data;
}

Node* Node::getNext() const { // 获取当前节点下一个节点的地址
    return m_next;
}

// 设置节点数据
void Node::setData(Type data) {
    m_data = data;
}

// 设置当前节点下一个节点的地址
void Node::setNext(Node *next) {
    m_next = next;
}

// =========================================================

// 构造函数
MyForward_list::MyForward_list() : m_size(0), m_first(nullptr), m_last(nullptr) {
    cout << "MyForward_list constructor" << endl;
}

// 析构函数
MyForward_list::~MyForward_list() {
    cout << "MyForward_list de_structor" << endl;
    clear();
}

// 拷贝构造函数(深拷贝,复制所有的数据节点到新链表)
MyForward_list::MyForward_list(const MyForward_list &list) {
    cout << "MyForward_list copy constructor" << endl;
    m_size = 0;
    m_first = m_last = nullptr;
    // 复制原链表的每一个数据节点(取原链表的每一个数据节点,加入到新创建的链表)
    Node *p = list.m_first;
    while (p) {
        push_back(p->getData()); // 获取节点的数据,加入到新链表
        // p = p->m_next; // 不可以在类外访问私有成员
        p = p->getNext();
    }
}

// 清空链表(删除所以的数据节点)
void MyForward_list::clear() {
    while (!isEmpty()) {
        pop_front();
    }
}

// 添加元素
void MyForward_list::push_back(Type data) { // 在链表尾部添加元素
    // 开辟节点保存数据
    Node *node = new Node{data};
    // 把节点加入到链表
    if (isEmpty()) {
        m_first = m_last = node;
    } else {
        // 尾插法
        // m_last->m_next = node; // 不可以在类外访问私有成员
        m_last->setNext(node);
        m_last = node;
    }
     m_size++;
}

void MyForward_list::push_front(Type data) {
    // 开辟节点保存数据
    Node *node = new Node{data};
    // 把节点加入到链表
    if (isEmpty()) {
        m_first = m_last = node;
    } else {
        // 头插法
        // node->m_next = m_first; // 不可以在类外访问私有成员
        node->setNext(m_first);
        m_first = node;
    }
    m_size++;
}

// 删除元素(删除第一个节点,并且返回数据)
Type MyForward_list::pop_front() {
    // 判断链表是否为空
    if (isEmpty()) {
        throw runtime_error{"Forward_list::pop_front::list is Empty!"};
    }
    m_size--;
    // 保存要删除的节点的数据
    Type value = m_first->getData();
    // 删除节点
    Node *node = m_first;
    // m_first = m_first->m_next; // 不可以在类外访问私有成员
    m_first = m_first->getNext();
    node->setNext(nullptr); // 设置当前节点下一个节点的地址
    // 释放节点
    delete node;
    // 如果删除节点后链表为空,则更新m_last为nullptr
    if (isEmpty()) {
        m_last = nullptr;
    }
    // 返回删除节点的数据
    return value;
}

// 获取元素个数
int MyForward_list::size() const {
    return m_size;
}

// 判断链表是否为空
bool MyForward_list::isEmpty() const {
    return m_size == 0;
}

// 查找元素(返回查找到的节点的地址,如果没找到返回nullptr)
Node* MyForward_list::find_value(Type data) {
    Node *p = m_first;
    while (p) {
        if (p->getData() == data) {
            return p;
        }
        p = p->getNext();
    }
    return nullptr;
}

// 打印元素
void MyForward_list::printList() const {
    Node *p = m_first;
    while (p) {
        cout << p->getData() << " ";
        p = p->getNext();
    }
    cout << endl;
}

main.cpp

#include <iostream>
#include "MyForward_list.hpp"

using namespace std;

int main() {
    
    MyForward_list l;
    
    l.push_back("aaa");
    l.push_back("bbb");
    l.push_front("ccc");

    cout << l.size() << endl;

    l.printList();

    Node *node = l.find_value("bbb");
    if (node != nullptr) {
        cout << "Found: " << node->getData() << endl;
    }

    l.clear();

    return 0;
}

4. 将指定目录下的图片全部保存到一个双向循环链表中去,再循环播放到6818开发板
MyList.hpp

#ifndef __MYLIST_HPP__
#define __MYLIST_HPP__

#include <iostream>

using namespace std;

using Type = int; // Type是int的别名
// typedef Type string; // Type是string的别名

// 定义双向循环链表类
class MyList {
    private:
        // 节点类
        struct Node {
            Type _data; // 节点拥有的用户数据
            Node *_prev; // 指向前一个节点的指针
            Node *_next; // 指向下一个节点的指针
            // 构造函数
            Node(Type data);
            // 析构函数
            ~Node();
        };
        int _size; // 链表中当前的节点个数
        Node *_head; // 指向链表第一个节点的指针
        Node *_tail; // 指向链表最后一个节点的指针
    public:
        // 构造函数
        MyList();

        // 析构函数
        ~MyList();

        // 拷贝构造函数
        MyList(const MyList &list);

        // 清空链表(删除所有的数据节点)
        void clear();

        // 添加元素
        void push_back(Type data);
        void push_front(Type data);

        Type front() const;
        Type back() const;

        // 删除元素
        void pop_front(); // 删除第一个节点
        void pop_back(); // 删除最后一个节点

        // 获取元素个数
        int size() const;

        // 判断链表是否为空
        bool isEmpty() const;

        // 打印元素
        void print() const;
};

#endif

MyList.cpp

#include "MyList.hpp"

// 节点类
// 构造函数
MyList::Node::Node(Type data) : _data(data), _prev(nullptr), _next(nullptr) {
    cout << "Node constructor" << endl;
}

// 析构函数
MyList::Node::~Node() {
    cout << "Node de-structor" << endl;
}

// =========================================================
// 双向循环链表类
// 构造函数
MyList::MyList() : _size(0), _head(nullptr), _tail(nullptr) {
    cout << "List constructor" << endl;
}

// 析构函数
MyList::~MyList() {
    cout << "List de-structor" << endl;
    clear();
}
 
// 拷贝构造函数
MyList::MyList(const MyList &list) : _size(0), _head(nullptr), _tail(nullptr) {
    cout << "List copy constructor" << endl;
    // 复制原链表的每一个数据节点(取原链表的每一个数据节点,加入到新创建的链表)
    Node *cur = list._head;
    int i = list._size;
    while (i--) {
        push_back(cur->_data); // 尾插
        cur = cur->_next;
    }
}

// 清空链表
void MyList::clear() {
    while (!isEmpty()) {
        pop_front(); // 头删
    }
}

// 添加元素
void MyList::push_back(Type data) {
    Node *newNode = new Node(data);
    if (isEmpty()) {
        _head = newNode;
        _tail = newNode;
        newNode->_next = newNode;
        newNode->_prev = newNode;
    } else {
        newNode->_next = _head;
        newNode->_prev = _tail;
        _tail->_next = newNode;
        _head->_prev = newNode;
        _tail = newNode;
    }
    _size++;
}

void MyList::push_front(Type data) {
    Node *newNode = new Node(data);
    if (isEmpty()) {
        _head = newNode;
        _tail = newNode;
        newNode->_next = newNode;
        newNode->_prev = newNode;
    } else {
        newNode->_next = _head;
        newNode->_prev = _tail;
        _tail->_next = newNode;
        _head->_prev = newNode;
        _head = newNode;
    }
    _size++;
}

Type MyList::front() const {
    if (isEmpty()) {
        throw "List is empty!";
    }
    return _head->_data;
}

Type MyList::back() const {
    if (isEmpty()) {
        throw "List is empty!";
    }
    return _tail->_data;
}

// 删除元素
void MyList::pop_front() {
    if (isEmpty()) {
        return;
    }
    Node *cur = _head;
    _head = _head->_next;
    _tail->_next = _head;
    _head->_prev = _tail;
    cur->_next = nullptr;
    delete cur;
    _size--;
}

void MyList::pop_back() {
    if (isEmpty()) {
        return;
    }
    Node *cur = _tail;
    _tail = _tail->_prev;
    _tail->_next = _head;
    _head->_prev = _tail;
    cur->_prev = nullptr;
    delete cur;
    _size--;
}

// 获取元素个数
int MyList::size() const {
    return _size;
}

// 判断链表是否为空
bool MyList::isEmpty() const {
    return _size == 0;
}

// 打印元素,打印两次
void MyList::print() const {
    cout << "List: ";
    Node *cur = _head;
    int i = _size * 2;
    while (i--) {
        cout << cur->_data << " ";
        cur = cur->_next;
    }
    cout << endl;
}

MyDir.hpp


MyDir.cpp

screen.hpp

screen.cpp

main.cpp


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值