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