C++基础总结(中)

tech2024-06-06  71

文章目录

I/O库一、IO类二、输出缓存1.矛盾2.缓冲刷新的原因(写入到文件或设备) 顺序容器一、迭代器二、细节三、容器适配器1.默认2.例子3.扩展 泛式算法一、标准库算法二、lambda三、参数绑定四、杂例1.再探迭代器2.list特定的容器算法 关联容器一、类型别名动态内存一、智能指针1.shared_ptr2.unique_ptr3.weak_ptr 二、动态数组1.构造与释放2.关于智能指针 三、allocator类总结


I/O库


一、IO类

IO对象无拷贝或赋值,甚至不能显式构造初始化

二、输出缓存

1.矛盾

CPU和IO设备的速度之间的矛盾,就决定我们不能把每一次IO操作都单独执行,所以系统会把多个IO操作组合成一个系统级操作,从而带来性能上的优化,而这些单个操作的数据就被放在缓冲区(缓冲区分为输入缓冲区和输出缓冲区)中,等CPU需要时再从缓冲区中获取。 带来的好处:

合并多次IO操作为一个系统级操作,有利于性能提升IO设备与缓冲区交互时,CPU可以运行其他进程

2.缓冲刷新的原因(写入到文件或设备)

程序结束,return,缓冲刷新被执行(所以程序结束,cout的数据会出现在显示器上)缓冲区满endl,flush,ends显示刷新unitbuf设置流的内部状态,清空缓冲区,cerr默认设置unitbuf cout << unitbuf << "hello world!" << endl; 关联输入输出流(x.tie(&o))——任何试图从输入流读取数据的操作都会先刷新关联的输出流 cin.tie(&cout);

顺序容器


一、迭代器

迭代器是一个公共接口,用于访问和遍历对应的容器forward_list迭代器不支持递减运算符(–),因为它是单向链表

二、细节

forward_list有自己的insert和emplace,而没有push_back和emplace_back,原因是forward_list是单向链表,只记录头节点,插入到表尾时间复杂度O(n),而list是双向链表,记录表尾,插入到表尾仅需O(1)forward_list和list的迭代器不能相减,因为它们的内存分配不是连续的 forward_list<int> vec{1, 2, 3}; cout << distance(vec.begin(), vec.end()) << endl; vector和string不支持push_front和emplace_front,同理插入O(n)emplace_back和push_back的区别: emplace_back会在容器管理的内存空间直接创建对象,而调用push_back则会通过构造函数创建一个局部临时变量,并调用拷贝构造函数将拷贝的对象压入容器,再释放临时变量 代码来源 #include <iostream> #include <vector> #include<string> using namespace std; class CText { private: string str; public: CText(string s) : str(s) { } void show() const { cout << str << endl; } }; int main() { vector<CText> vi; vi.emplace_back("hey"); vi.front().show(); vi.push_back("girl");//错误 vi.back().show(); return 0; } 容器操作可能使迭代器失效(这张图片里关于deque插入删除规律还是不太明白原理,估计得去了解一下deque的内存模型,才能知道答案,这里先留个坑,待填!)

三、容器适配器

1.默认

stack和queue基于deque实现priority_queue基于vector实现

2.例子

stack<string,vector<string>>str_stack//在vector上实现的空栈

3.扩展

array,forward_list不能用来构造适配器。因为所有适配器具有添加,删除以及访问尾元素,array是不可变数组,不能添加和删除,而forward_list不能O(1)访问尾元素表格(priority_queue具有随机访问能力,不过被隐藏了) 适配器类型需要支持的操作满足的容器stackpush_back,pop_back,back除了上面两个(下面不再阐述)queueback,push_back,front,push_frontlist和deque都可以,但vector不行priority_queuefront,push_back,pop_back,随机访问能力vector,deque,不能基于list

泛式算法


一、标准库算法

对迭代器而不是容器操作,因此算法不能直接添加或删除元素,例如unique不能删除重复元素,而是将重复元素移到后面只读算法——accumulate:接受3个参数,前两个是需要求和的元素范围,第三个参数是和的初值,同时第三个参数类型决定了函数使用哪个加法运算符和返回值的类型写算法——向目的位置迭代器写入数据的算法假定目的位置足够大,能容纳要写入的元素(位置足够大代表序列大小,并非可用内存大小,即reserve无效,resize才可以)

二、lambda

格式:[capture list](parameter list)->return type {function body}尾置返回(参考)如果lambda函数体包含任何单一return语句之外的内容,且未指定返回类型,则返回void捕获列表:捕获列表只用于局部非static变量,lambda可以直接使用局部static变量和它所在函数之外声明的名字可变lambda: 值捕获——lambda不会改变捕获的拷贝变量,加上mutable即可修改 引用捕获——取决于此引用指向的是一个const类型还是非const类型,const类型不可修改 int main() { int x = 10; auto it = [&x]()//引用捕获,去掉&就是值捕获,但会报错,因为值捕获不能修改捕获的拷贝变量 { x = 9; }; it(); cout << x << endl; return 0; }

三、参数绑定

谓词(一元——find_if;二元——sort)

bool check_size(const string &str, size_t sz) { return str.size() >= sz; } int main() { string str = "hello world"; auto it = bind(check_size, placeholders::_1, 6); cout << it(str) << endl; return 0; }

find_if传入的函数对象是一元,即对应函数实参只有一个,但有时需要两个,这里可以采用bind绑定部分参数,从而使真正find_if传入的只有一个实参(这时有人可能想到给形参设置默认值,但运行会报错,还是得用bind绑定) placeholders::_1顾名思义就是待放置的孔,就是真正需要传入的实参

vector<string> vec{"hello", "world", "C++ Primer"}; auto val = find_if(vec.begin(), vec.end(), bind(check_size, placeholders::_1, 6)); cout << *val << endl;

一个小问题:bind对绑定参数的处理是拷贝,但IO类无法拷贝,只能引用,需要进行的操作就是把绑定参数设置成引用的,可以使用ref()函数

#include <iostream> #include <vector> #include <string> #include <functional> using namespace std; ostream &print(ostream &os, const string &str, char ch) { os << str << ch; return os; } int main() { vector<string> vec{"hello", "world", "C++ Primer"}; for_each(vec.begin(), vec.end(), bind(print, ref(cout), placeholders::_1, '\n')); return 0; }

四、杂例

1.再探迭代器

back_inserter,创建一个使用push_back的迭代器front_inserter,创建一个使用push_front的迭代器inserter,创建一个使用insert的迭代器 //it是调用inserter(c,iter)得到的一个迭代器 *it=val; | V it=c.insert(it,val);//指向新添加的元素 ++it;//指向原来的元素

2.list特定的容器算法

其他容器算法不会改变容器大小,但list和forward_list会改变容器大小,例如list的unique就会直接删除重复的,因为链表的内存空间不是连续的,所以可以直接删除而不破坏迭代器


关联容器


一、类型别名

key_type——关键词类型mapped_type——关键词关联类型,只适用于mapvalue_type——对于set,与key_value相同;对于map,为pair<const key_type,mapped_type>

动态内存


一、智能指针

1.shared_ptr

构造函数——explicit,接受指针参数,需要直接初始化 正确例子:shared_ptr<int>p(new int(1024));//显示构造 错误例子:shared_ptr<int>p=new int(1024);//隐式构造 陷阱 1.不使用相同的内置指针值初始化多个智能指针 int main() { /* 这是一段错误代码,使用了相同的内置指针初始化智能指针 这两个智能指针的引用计数都为1,也就是说第一个智能指针 脱离作用域后,引用-1,对应那块内存被销毁,第二个智能 指针同理,即同一块内存被析构了两次 */ int y = 10; int *x = &y; shared_ptr<int> ptr(x); shared_ptr<int> ptr_1(x); cout << ptr.use_count() << endl;//1 cout << ptr_1.use_count() << endl;//1 return 0; }

2.不delete get()返回的指针。delete get()的,智能指针又会再释放一次 3.不使用get()初始化或reset另一个智能指针。同1,get返回的也是内置指针 4.当你使用的智能指针管理的资源不是new分配的内存,记住传递一个删除器

2.unique_ptr

"拥有"所指对象直接初始化(不接受非new返回类型的指针,如*pi=&x等),不支持普通的拷贝或赋值操作 int main() { int *x = new int(10); unique_ptr<int> ptr(x); unique_ptr<int> cptr(new int(20)); return 0; } 不支持普通的拷贝或赋值操作,但可以拷贝或赋值一个将要被销毁的unique_ptr,例如函数 unique_ptr<int> fun() { return unique_ptr<int>(new int(10)); }

3.weak_ptr

不控制所指向对象生存期的智能指针,指向由一个shared_ptr管理的对象绑定到shared_ptr不会改变shared_ptr的引用计数引用计数为0,即使有weak_ptr指向对象,对象也还是会被释放

二、动态数组

1.构造与释放

new可以分配大小为0的数组,返回一个合法非空指针。此指针不能解引用,因为它不指向任何元素数组元素按逆序销毁,首先销毁最后一个元素

2.关于智能指针

关于unique_ptr—— 不能使用点和箭头成员运算符,因为指向的是一个数组; 当一个unique_ptr指向一个数组时,可以使用下标运算符来访问数组中的元素; 总的来说就是一个数组指针。关于shared_ptr—— 不直接支持管理动态数组,除非提供自定义的删除器; 默认情况下使用delete删除,但对象是一个动态数组(非vector),对其使用delete所产生的问题与释放一个动态数组时忘记[]产生的问题一样——行为未定义; 访问:*(sp.get()+i)=i(需要获取一个内置指针,使用get())

三、allocator类

与new的不同——new将内存分配和对象构造组合在一起。allocator将内存分配和对象构造分离,所以分配的内存是原始的,未构造的allocator和malloc的区别——简单的说C++中,每次malloc后都要强转一下类型,allocator就不用(肯定不止这么简单,如果有兴趣的可以再深入研究…)

总结


这就是《C++ Primer》的第二部分的基础总结了,总结的很简陋,如果想更加深入的了解,还是自己看一下《C++ Primer》比较好…

最新回复(0)