c++ primer复习(3)那些太容易被忽略的细节(持续更新)

c++ primer复习

1.std::endl 有一个作用是来冲洗buffer

2.声明语句时理解方式typedef

typedef char* pstring;
//这两看似一样,但是实际不同
const pstring cstr = 0;	//char*为基本数据类型
const char* cstr = 0;   //char 为基本数据类型,*变成声明

3. const和constexpr

常量表达式:
constexpr 指定的一定是常量表达式(编译期可求值);
constexpr受到的限制,constexpr指针必须是nullptr或0,或者是存储于某个固定地址中的对象.

const和constexpr的区别:
对象常量性可分为两种: 物理常量性(每个bit都不可能改变)和逻辑常量性(对象的表现保持不变).
C++中采用的是物理常量性.
例如:

class ME{
public:
	int *p;
}

int a = 5, b = 6;
const ME tmpME = {&a};		//编译器创建的构造函数
tmpME.p = &b;				//编译器报错:tmpME.p指针所指地址不能变
*(tmpME.p) = 10;			//不报错: tmpME.p的地址没变,地址保存的"值"变了

constexpr值可用于enum,switch,数组长度等场合,有很强的约束更安全,编译器对constexpr的优化很大,可将用到constexpr表达式的地方直接替换成最终结果.

3.auto声明

多条声明语句类型必须一致

auto i=0,*p=&i;

4.std::size_t

是一种std::string里内置的变量类型,是无符号的,所以要小心跟负值的int比较

5.std::string 字符串相加遵守原则:

两相加的对象中至少一个必须是string

6.std::string 类的输入运算符和getline函数分别是如何处理空白字符的

输入运算符 自动忽略空格,制表符(/t),换行符(/n), getline保留输入时的空白符.

7.include头文件

处理的一组标准函数.用于字符串的处理和判断
但是输入时 int _C ,用于检测单个字符类型的库吧.
在这里插入图片描述

8.decltype(things)

用于检测things的对象类型
可用于一些类型模糊的对象提取他们的对象类型

string str("hello");
decltype(str) punct_cnt = 0;			//类型是:std::size_t

9.std::string的下标合法性

//合法不合法跟运行成不成功没啥关系
//下标合法性,就是说0~string.size()之间的下标为合法(c++primer说的),不合法即下标超过size()
//所以string是空时 0~0是合法的下标
//这句能运行成功
//string为空,则s[0]的结果将是未定义的.s[1]就直接报错

std::cout <<"输出空字符串中的tt[0]:"<< tt[0] << std::endl;

10.初始化列表在vector中应用时

vector<string> v5{"hi","me"};		//列表初始化,v5有2个元素
vector<string> v6("hi");			//错误:不能使用字符串字面值构建vector对象
vector<string> v7{10};				//v7有10个默认的初始化元素
vector<string> v8(10,"hi");			//v8有10个值为"hi"的元素

11.遍历vector时顺便修改值

std::vector<int> v{ 1,2,3,4,5,6,7,8,9 };
for (auto &i : v)       //可以修改v的值
	i *= i;
for (auto i : v)
	std::cout << i << " ";
std::cout << std::endl;

vector对象的下标只能用于访问已存在的元素,而不能用于添加元素;
下标形式访问不存在的元素不会被编译器发现,而是在运行时产生一个不可预知的值,即缓冲区溢出错误(buffer overflow);

12.简化vector访问元素的方法->箭头运算符

//字符串向量中对象为空时停止循环,关键点位it->empty() 检测字符串是否为空
//it->empty() 访问 string对象,比起(*it).empty()更便捷
std::vector<std::string> text = {"wo","cao","li","","made","bi","zui"};
for (auto it = text.cbegin(); it != text.cend() && !it->empty(); ++it)
	cout << *it << endl;

13.vector的一个限制

:不能在for循环中向vector对象添加元素,但是可以删除元素.

14.迭代器位置类型名为 difference_type 的带符号整型数,因为距离可正可负

15.数组由内向外,由右向左解读

int *(&array)[10] = ptrs;		//arry是数组的引用,该数组含有10个指针

首先知道arry是个引用,然后观察右边知道arry引用的对象时一个大小为10的数字,最后观察左边知道,数组的元素类型是指向int的指针.

int (*parray)[10] = &arr;  		//parray指向一个含有10个整数的数组

首先圆括号起来的部分意味着parray是个指针,接下来观察右边可知oarray是个指向大小为10的数组指针
最后看左边可知数组元素时int型.

int *ptrs[10];					//ptrs是含有10个整型指针的数组

从右向左,首先可知定义的是一个大小为10的数组,他的名字是ptrs,然后再看类型是(int*)整型指针

char st[11] = "fundamental" ;   //错误,没有空间放空字符串"\0"

16.数组与vector的区别

数组运行时性能较好,但是不能像vector随意增加元素,损失了灵活性

//c++ primer 第五版 练习题 3.28 

//问下列数组中元素的"值"是什么?
#include <iostream>
using namespace std;

string sa[10];
int ia[10];						//没明白为什么这个全局变量是会自动初始化,后续再找找补充(以补充)
char a[5];						//也会被初始化为空字符串
int main(){
	string sa2[10];
	int ia2[10];			
}

//sa,sa2里面的值都10个空字符串
//ia里面是10个0值
//ia2里面是10个-858993460(垃圾值:未初始化的值)
//可以看到c的值为-858993460这个值即为0xcccccccc的十进制表示。
//原因是:未初始化的栈区编译器默认(在vs2017下)都按照cc去填充了。另外补充,在gcc编译器则是按照0填充的

(补充如下)
默认初始化规则:
定义基本数据类型变量的同时可以指定初始值,如果未指定会默认初始化
1.栈中的变量和堆中的变量会保有不确定值

2.全局变量和静态变量(包含局部静态变量)会初始化为零

静态和全局变量的初始化
未初始化的和初始化为零的静态/全局变量编译器是同样对待的,把它们存储在进程的BSS段(这是全零的一段内存空间)中.所以它们会被"默认初始化"为零

成员变量的初始化
成员变量分为成员对象和内置类型成员,其中成员对象总是会被初始化. 而我们要做的就是在构造函数中初始化其中的内置类型成员.
内置类型的成员变量的"默认初始化"行位取决于所在对象的存储类型,而存储类型对应的默认初始化规则时不变的.所以为了避免不确定的初值,通常会在构造函数中初始化所有内置类型的成员.

封闭类嵌套成员的初始化
同样还是只关注于基本数据类型的成员.取决于当前封闭类对象的存储类型,而存储类型对应默认初始化规则仍然是不变的

17.c++ 11标准引入的两个名为 begin和end的函数(头文件iterator)

// 头文件iterator
int a[] = {0,1,2,3,4};
int *beg = begin(a);		//指向a的首元素
int *last = end(a);			//指向a的尾元素下一位置指针,注意尾指针不能解引用和递增

18.给指针加上一个整数,得到的新指针仍需指向同一数组的其他元素,或者指向同一数组的尾元素的下一位置:

constexpr size_t sz = 5;
int arr[sz] = {1,2,3,4,5};
int *p = arr + sz;		//使用警告: 不要解引用
int *p2 = arr + 10;  	//错误:p2的值未定义
//编译器发现不了,程序运行到这里就会指针越界崩溃

int *b = arr, *e = arr + sz;
while(b<e){++b;}
//如果两个指针分别指向不相关的对象,就不能比较

19.c风格字符串函数注意:

strlen,strcmp(p1,p2),strcat(p1,p2),strcpy(p1,p2)
此类函数的指针必须以空字符串为结束的数组.

20.下面程序运行结果是什么?

void test_337()
{
    const char ca[] = { 'h','e','l','l','o' };
    const char* cp = ca;        //const 表示所指物为常量
    while (*cp) {
        cout << *cp << endl;
        ++cp;
    }
}

// 因为ca是C风格字符串,字符串结尾必须带'0'
// 字符串ca在内存中的位置不断向前寻找直到遇到空字符串才停下来

21.两个指针相加为何没有意义

因为相加后的指针指向的值,跟相加的两个指针没什么关系,而且指向的值也不是我们想要的结果.

22.能用数组初始化vector,通过指定指针首尾

//需要头文件#include<vector>
int int_arr = {1,2,3,4,5};
vector<int> ivec(begin(int_arr),end(int_arr));		//begin和end需要头文件#include<iterator>
//也可以部分赋值
vector<int> subvec(int_arr + 1,int_arr +4);			//赋值int_arr[1]~[3]
// 列表初始化vector
vector<int> v{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

不允许使用一个数组为另一个内置类型的数组赋初值
不允许使用vector初始化数组

23.auto在for中使用时注意把变量声明为引用

for循环使用auto时变量声明为引用类型为了避免数组被自动转成指针

constexpr size_t row = 3,col = 4;
int nums[row][col];
for(const auto &row:nums)
	for(auto col:row)
		cout<<col<<endl;
		
for(auto row:nums)
	for(auto col:row)		//报错,无法通过编译

//要使用范围for语句处理多维数组,除了最内层的循环外
//其他所有循环的控制变量都应该是引用类型

24.没有指定顺序的运算符<<

int i = 0;
cout << i << " " << ++i << endl;   //有可能是1 1,也有可能是0 1,称之为"未定义的行为"
//甚至编译器还可能做完全不同的操作,因为此表达式的行为不可预知

25.四种运算符明确规定了运算对象的求值顺序

逻辑与(&&)运算符,规定先求左侧运算对象的值,只有左侧运算值为真才继续求右侧运算的值
逻辑或(||)运算符,
条件(?:)运算符,
逗号(,)运算符,

    bool b = true;
    bool b2 = -b;		//-b 为 1,所有非0数为true

26.m%(-n) 等于 m%n,(-m%n)等于-(m%n)

27.溢出:当计算的结果超出该类型所能表示的范围时产生溢出

1/0;		//除数是0
short svalue = 32767; //数值上溢,超过最大值 svalue+1 ,等于 -32768,不同系统会有其他结果
float floatVar = 3.OE-47;	//数值太小导致下溢,等于 0

28. 符号的优先级有关

// if(i<j<k) 和 if(i != j < k)
(i<j<k)意思:拿 i<j 的布尔结果和k比较
(i != j < k)意思:拿j<k的布尔结果跟i比较

29.赋值"="

int i;
double d;
d = i = 3.5; //3 3
i = d = 3.5; //3 3.5

30.*pbeg++ 等价于 *(pbeg++)

这条语句输出pbeg开始时指向的那个元素,并将指针向前移动一个位置
解引用运算符*,优先级低于点运算符.

31.说明前置递归运算符和后置递增运算符的区别

前置递增运算符会先把对象值加一,求值结果是加一后的值
后置递增运算符会先保存对象值的副本再加一,求值结果是保存的副本

32.运算符优先级

https://www.sojson.com/operation/cxx.html

std::vector<string> emt = { "leimu","lks","liyuanjun","小姐姐" };
std::vector<string>::iterator it = emt.begin() + 3;
cout<<*it<<" " <<  endl;
cout << it++->empty() << endl;

33.条件运算符和cout,使用中注意括号

int grade;
cin >> grade;
cout << ((grade < 60) ? "fail" : "pass") << endl;
//cout << (grade < 60) ? "fail" : "pass" << endl;
//等价于
//cout << (grade < 60);
//cout ? "fail" : "pass";

//cout << grade < 60 ? "fail" : "pass" << endl;
//等价于
//cout << grade;
//cout < 60 ? "fail" : "pass";

34.左移运算符

unsigned long quiz = 0;
//1ul的低阶上有个1,除此之外还有31个值为0的位,表达式使它做移动了27位
1ul<<27

//quiz 进行位或运算
quiz |= 1ul<<27;		//27位强制置为1

//quiz 进行 位与运算 位求反运算
quiz &=~(1ul<<27);		//27位置为0

如果unsigned long改为unsigned int会发生什么情况?
答:
因为unsigned int 只能确保占用16位,而我们至少需要27位,位移后值有可能会变成0

35.移位运算符(IO运算符)满足左结合律

cout<<"hi"<<" there"<<endl;
相当于
(cout<<"hi")<<" there"<<endl;

移位运算符:
优先级大于关系运算符,使用时注意括号, cout<<10 <42; //错误:试图比较cout和42
优先级小于算术运算符(+,-,*,/ 等)

36.比较有趣的位运算

unsigned long ulo = 3,ult = 7; //分别是 0011 ,0111

ulo & ult = 3;
ulo | ult = 7;
ulo && ult = 1;
ulo || ult = 1;

37.左右结合律

左结合律:表达式按照从左向右的顺序组合

右结合律:表达式按照从右向左的顺序组合

//sizeof满足右结合律
class Atom;
Atom *p;
sizeof *p;  //相当于 sizeof(*p)
//在sizeof的运算对象中解引用一个无效指针仍然是一种安全行为
//因为指针实际上没被使用,sizeof没有解引用p也能知道它所指对象的类型

38.位运算判断奇数

i & 0x1

39.左值,右值

左值:
具有对应的可以由用户访问的存储单元(变量),并且能够由用户去改变其值的量。左值表示存储在计算机内存的对象,而不是常量或计算的结果,或者说左值是代表一个内存地址值

右值:
代表的真实值。简单来说就是,左值相当于地址值,右值相当于数据值。右值指的是引用了一个存储在某个内存地址里的数据(const等数据)

40.为什么要用前置递增递减运算符?

std::vector<int> ivec1 = {9,2,3,4};
std::vector<int> ivec2 = { 9,2,3,4 };
std::vector<int>::size_type cnt1 = ivec1.size();
std::vector<int>::size_type cnt2 = ivec2.size();
for (vector<int>::size_type ix = 0; ix != ivec1.size(); ++ix, --cnt1)
	ivec1[ix] = cnt1;

for (vector<int>::size_type ix = 0; ix != ivec2.size(); ix++, cnt2--)
	ivec2[ix] = cnt2;
//结果:两个向量里的值都一样

41.运算符优先级和结合律之间的注意点

//说明表达式的含义
someValue ? ++x, ++y : --x, --y;

//如果加入括号转化后相当于
(someValue ? ++x, ++y : --x),( --y);
//?: 是右结合律的运算符,所以先执行--y,逗号优先级最低最后看
//后执行?:的运算符

42.类型转换

  1. 隐式转换:

    指编译器自动转换,不需要程序员介入;

  2. 算术转换:

    • 整型提升:
      小整数类型转换成较大整数类型,
      (bool,char,signed char,unsigned char,short和unsigned short 等 转换为 int)
      如bool false提升为0,true提成为1;
      前提是转换后的类型能容纳原类型所有可能的值

    • 无符号类型的运算对象:
      运算符的运算对象类型不一致,运算对象将转换成同一种类型;
      要注转换对象类型是无符号类型时,有符号类型转换时的副作用
      1.如果无符号类型对象值 >= 带符号类型,带符号类型转换为无符号类型
      2.如果无符号类型 < 带符号类型,无符号类型转换为带符号类型
      (如果不能,那带符号的转换成无符号)
      (例子1:long和unsigned int,int和long大小相同,则long类型的运算对象转换成unsigned int类型)
      (例子2:long类型占用空间比int多时就不能转换,所以unsigned int转换为long类型)

理解算术转换:

bool flag;
char cval;
short sval;
unsigned short usval;
int ival;
unsigned int uival;
long lval;
unsigned long ulval;
float fval;
double dval;

3.1415L + 'a' ; //'a'提成为 int,然后int值转换成long double
dval + ival;	//ival转换成double
dval + fval;	//fval转换成double
ival = dval;	//dval转换成int,丢弃小数部分
flag = dval;	//dval是0,则false,dval非0则为true
cval + fval;	//cval转换成float
ival + ulval;	//ival转换成 unsigned long
usval + ival;	//根据unsigned short和int 所占空间大小进行提升
uival + lval;	//根据unsigned int和long所占空间的大小进行转换

指针的转换:

  1. 常量整数值0或者nullptr能转换成任意指针类型
  2. 指向任意非常量的指针能转换成void*
  3. 指向任意对象的指针能转换成const void*

转换成常量:
1.允许将指向非常量类型的指针 转换成指向相应的常量类型的指针,引用也是如此
2.不允许 删掉底层const的转换

类类型定义的转换:
编译器每次只能执行一种类型的转换,同时提出多个转换请求,会被拒绝

43.显示类型转换

static_cast:
对于编译器无法自动执行的类型转换,例如void* 转换为可知的值

dynamic_cast

const_cast: 常量对象转换为非常量对象
1.只能改变对象底层的const,只能改变常量属性
2.能改变表达式的常量属性

reinterpret_cast: 位模式(二进制)低层次上的重新解释,比较危险
变奇迹无法知道它实际存放的是指向其他类型的指针.

44.再一次复习const:

1.顶层const: 指针本身是常量.即不能改变指针的指向,一旦指定不能改变

2.底层const: 指针所指的是一个常量.即不能改变所指对象的值,值是常量

45.空语句{}:

语法上需要一条语句但是逻辑上不需要,此时应该用空语句

46.复合语句(块):

指用花括号括起来的语句和声明的序列,表示一个作用域
应用:语法上需要一条语句,但是逻辑上需要多条语句.

47.if_else语句:就近匹配

c语言的一些函数:
ctype.h
int tolower(char c); //字母变为小写字母
int isupper(char c); //判断字母是否是大写

break语句的范围仅限于最近的循环或者switch

48.string read;

标准输入: cin>>read;
//如果此时read中存在空格的话,需要while(cin>>read)来读取后续的内容
//所以这也可以做成用字符串的判断,连续出现2次相同字符串的功能.
//cin.eof();//用于判断cin中的内容是否读取完毕.
//cin.get();//从字节流中(cout中)获取单个字符串

49.异常处理: try,throw

throw runtime_error(""); //runtime_error是标准库异常类型的一种,定义在stdexcept头文件中。
必须初始化runtime_error的对象,方式是给他提供一个string对象或者一个C风格的字符串
catch(runtime_error err)
{
cout<<err.what()<<endl; //err.what()输出runtime_error初始化的内容
}

异常类:(std:😃
定义的异常类
exception: 最常见的问题,只报告异常发生,不提供任何额外信息
runtime_error: 只有运行时才能检测出的问题
range_error: 运行时错误: 生成的结果超出了有意义的值域范围
overflow_error: 运行时错误:计算上溢
underflow_error:运行时错误:计算下溢
logic_error: 程序逻辑错误
domain_error: 逻辑错误:参数对应的结果值不存在
invalid_argument:逻辑错误:无效参数
length_error: 逻辑错误:试图创建一个超出该类型最大长度的对象
out_of_range: 逻辑错误:使用一个超出有效范围的值

new头文件定义:
bad_alloc:

type_info头文件定义:
bad_cast:

bad_alloc和bad_cast需要用默认初始化(即编译器自己初始化).
其他异常(例如:runtime_error…这种),需要使用string对象或者C风格字符串初始化.

函数

50.实参和形参的区别? 实参是形参的初始值

51.dll开放接口声明的一种方式:

使用声明符:

__declspec(dllexport)
__declspec(dllimport)

52.指针作为形参,函数内部是指针的拷贝,拷贝之后两个指针是不同的指针,所以可以通过指针改变它所指对象的值,但是这跟指针之间的赋值又是另一码事情

void reset(int *ip)
{
*ip = 0;//成功改变指针ip所指的值
ip = 0 ;//只改变了ip这个形参的局部拷贝的指向,并没有改变实参的指针的指向
}

53.在c++中,允许我们定义若干具有相同名字的函数,不过前提是不同函数的形参列表应该有明显的区别.如下:

void func(cosnt int i);
void func(int i); //错误
因为顶层const被忽略掉了,所以使得func函数的参数可以完全一样,因此第二个fcn是错误的.
因为拥有顶层const的形参无法和另一个没有顶层const的形参区分开来。

54.使用普通引用会极大的限制函数所能接收的实参类型

普通引用(xx &)和常量引用(const xx&),就以下面两个例子来讲

bool was_empty1(const string& s)
{
return s.empty();
}
bool was_empty2(string& s){//…}

was_empty1(""); //正确
was_empty2(""); //错误,因为不能把const对象转换为普通引用类型

55.重载和const形参

int lookup(int );
int lookup(const int);
这两个声明是等价的,编译器会提示重复声明的提示,因为顶层const的形参无法和另一个没有顶层const的形参区分开来

int lookup(int *);
int lookup(double *);
这两个是可以区分开的,可以通过区分其指向的是常量还是非常量对象,可以实现重载,此时是底层const

int get();
double get();
无法重载仅按返回类型区分的函数.

56.constexpr 函数

//不能生成常量表达式
constexpr bool is_shorter(const string& lft, const string& rht) 
{
    return lft.size() < rht.size();
}

57. 调试预处理宏 (assert,NDEBUG)

  • assert(x) : 当x是0时会输出信息终止程序执行, 需要头文件 assert.h , assert是宏所以不需要带std::
  • NDEBUG 预处理变量 : NDEBUG写入到预处理列表后就不会触发 assert,#ifndef NDEBUG…#endif 之间的代码也不会触发

58.std::cout,std::cerr 的差别

  • cerr的数据不被缓冲,即会每写一个字母,就输出一个字母,然后刷屏. **目的:**程序遇到调用栈用完了的威胁.

  • cout会缓冲数据,所以栈空间满时就输出不了.

59.好用的预处理 func,file,time,date,line

__func__ //表示当前函数名
__FILE__ //存放文件名的字符串字面值
__TIME__ //存放文件编译时间的字符串字面值
__DATE__ //存放文件编译日期的字符字面值
__LINE__ //存放当前行号的整型字面值
void Pre_test()
{
    std::cerr << __LINE__ << " " << __DATE__ << " " << __TIME__ << " " << __func__ <<" "<<__FILE__<< std::endl;
}

结果:

请添加图片描述

60.函数匹配:

  • 候选函数:
    1. 与被调用函数同名
    2. 生命在调用位置可见
  • 可行函数:
    1. 形参数量与本次调用提供的实参数量相同
    2. 每个实参类型与对应的形参类型相同

61.函数指针

  • 概念: 指跟函数返回值和参数类型决定

    bool lengthtt(const string&);

    指向该函数的指针定义: 函数名改为指针

    bool (*pfunc)(const string&);

  • 函数指针赋值:

// 下面2中赋值效果一样
pfunc = lengthtt;
pfunc = &lengthtt;
  • 函数指针调用:
// 下面三种调用方式效果一样
bool b1 = pfunc("hell");
bool b2 = (*pfunc)("hell");
bool b3 = lengthtt("hell");
  • 函数指针重载:

    编译器通过指针类型自动选择, 指针类型必须精确匹配重载函数中的某一项.

  • 函数指针形参:

// 下面2个函数指针形参效果一样
void userBigger(const string &s1,const string& s2,bool pf(const string&,const string&));
void userBigger(const string &s1,const string& s2,bool (*pf)(const string&,const string&));
userBigger(s1,s2,lengthtt);	//调用

// Func 和 Func2 是函数类型
typedef bool Func(const string&,const string&);
typedef decltype(lengthtt) Func2;
// FuncP 和 FuncP2 是函数指针类型
typedef bool (*FuncP)(const string&,const string&);
typedef decltype(lengthtt) *FuncP2;
// 上述两种类型效果一样,注意:decltype只会返回函数类型,需要加*才是函数指针

// 使用typedef后简化的函数指针形参格式,下面两个效果一样
void userBigger(const string&,const string&,Func);//编译器自动转化为函数类型转换成指针
void userBigger(const string&,const string&,FuncP);//也可以是FuncP2
  • 返回指向函数的指针
// 1.使用类型别名
using F = int(int*,int);		//函数类型
using PF = int(*)(int*,int);	//函数指针类型
PF f1(int);
F f1(int);	//错误,F是函数类型,f1不能返回一个函数
F *f1(int);	//正确,显示的指定返回类型为指向函数的指针
// 2.直接声明
int (*f1(int))(int*,int);
// 3. 置尾返回类型方式
auto f1(int) -> int (*)(int*,int);
// practice 6.54 6.55 6.56
int funccc(int a, int b);
using pFunc1 = decltype(funccc)*;
typedef decltype(funccc)* pFunc2;
using pFunc3 = int (*)(int a, int b);
using pFunc4 = int(int a, int b);
typedef int(*pFunc5)(int a, int b);
using pFunc6 = decltype(funccc);

void test_vectorFunc2()
{
    //std::vector<pFunc1> func_vec;
    //std::vector<pFunc2> func_vec;
    //std::vector<pFunc3> func_vec;
    //std::vector<pFunc4*> func_vec;
    //std::vector<pFunc5> func_vec;
    std::vector<pFunc6*> func_vec;
    func_vec.push_back(add);
    func_vec.push_back(del);

    for (auto it : func_vec)
    {
        std::cout << it(8, 9) << std::endl;
    }
}

62.定义抽象数据类型

基本思想: 数据抽象和封装, 接口和实现分离编程;

设计者考虑类的实现过程,使用者考虑类型做了什么;

  • const成员函数: 作用是修饰隐式this指针类型

    • 防止成员函数修改被调用对象的值,即不能改变this指向的任何内部变量,所以一般叫做只读函数.
    • const修饰的函数不能访问static修饰的静态成员函数,静态成员不含有this指针,但静态变量是可以访问也可以改值,即没有用隐藏this修饰的变量就不受const修饰符控制
    • 一个const函数只能调用其他const函数
  • 成员函数和类作用域

    • 编译器分两步处理类: 首先编译成员声明, 后成员函数体
    • 所以可以在类外部定义成员函数体,需要用类名作为作用域::
  • 返回this对象函数

    My_Data& combine(const My_Data &rhs)
    {
        this->unit += rhs.unit;
        //返回解引用的对象,因为返回类型是&,需要返回对象,而不是对象指针
        return *this;
        
        //但如果是 My_Data* 就直接返回this就行.
        //this 的类型是 My_Data *const
    }
    
  • 默认构造函数 = default(c++11标准,请求编译器来生成构造函数)

  • 构造函数初始列表 : 冒号和花括号之间的代码

63.访问控制与封装

  1. 访问说明符的概念: public,private,protected 这些就是,在类中出现次数不受限制

private部分封装(即隐藏了)类的实现细节.

  1. struct 和 class 关键字定义类仅仅是形式上有所不同

struct和class唯一区别: struct和class的默认访问权限不太一样

struct的访问说明符默认是public,class则是private

  1. 封装: 分离接口和实现,隐藏实现细节

封装的好处:

  • 用户代码不会破坏封装对象的状态
  • 封装类的实现可以随时间变化,而不用修改用户的代码
  1. 友元: 在类中声明友元,可以让友元函数或者类访问private的数据
class Sales_data{
//友元,这个函数在别的地方有声明和实现
//因为public数据本来就可以访问这里只是声明让类知道这个函数可以访问private数据
friend std::istream &read(std::istream&,const Sales_data&);

public:
	Sales_data() = default;	//c++11 默认构造写法
private:
	std::string bookNo;
	double revenue = 0.0;
}

64.类的其他特性

  • 内联(inline): 在类内部的成员函数(即声明和定义都在类内)的自动是inline.

  • 可变数据类型(mutable): 声明为mutable类型的数据,即时是在一个const成员函数内,也可以修改数据

    class Screen{
    public:
        void some_member() cosnt;
    private:
        mutable size_t access_ctr;	
    }
    void Screen::some_member() const
    {
        ++access_ctr;
    }
    
  • c++11特性类内初始值: 需要给构造函数传递一个符合成员函数类型的实参

    class Screen{
    public:
        typedef std::string::size_type pos;
    private:   
        pos cursor = 0;
        pos height = 0,width = 0;
        std::string contents;
    }
    class Window_mgr{
    private:
        // 默认情况下,一个window_mgr包含一个标准尺寸的Screen
        std::vector<Screen> screens{Screen(24,80,' ')};
    }
    // 当我们提供一个类内初始值时,必须以符号=或花括号表示
    
  • 问题: Screen为什么能安全的依赖于拷贝和赋值操作的默认版本? 或者为什么不能?

    当类需要分配类对象之外的资源时,默认的赋值和拷贝操作可能会失效,比如包含动态内存的类,但vector和string能正常进行拷贝或赋值操作

  • 公共代码使用私有功能函数原因:

    • 公共代码可能变得更复杂
    • 公共函数可以在多个函数中调用私有函数
    • 私有函数设置成内联函数,也可以不增加开销
  • 使用this指针,显示的使用指针访问成员的优缺点

    优点: 1. 更明确 ; 2.减少误读概率; 3.可以使用相同名称的成员变量 this->aa = aa;

    缺点: 1. 要读的字符变多; 2. 冗余,没必要

  • 类之间的友元关系

    //当一个类想访问另外一个类中的私有成员
    class Screen{
    	// window_mgr 的成员可以访问Screen类的私有成员
    	friend class window_mgr;
    	
    	// 令成员函数作为友元,但要确保
    	friend void window_mgr::clear(ScreenIndex);
    }
    
    class window_mgr{
    public:
    	// 窗口中每个屏幕的编号
    	using ScreenIndex = std::vector<Screen>::size_type;
    	// 按照编号将制定的Screen重置为空白
    	void clear(ScreenIndex);
    	// 这个友元函数并不能访问Screen类中的私有成员
    	friend guess();
    }
    void window_mgr::clear(ScreenIndex i)
    {
    	// s是一个Screen的引用,指向我们想清空的那个屏幕
    	Screen &s = Screens[i];
    	// 将选中的Screen重置为空白
    	s.contents = std::string(s.height*s.width,' ');
    }
    

65.类的作用域

注意: 当某种返回类型是类内定义的类型时,定义函数体时要明确指定哪个类定义了他

比如: window_mgr::ScreenIndex;

//ScreenIndex是在windos_mgr类中定义的

//using ScreenIndex = std::vector::size_type;

  • 定义在类外部时:

    1. 首先在名字所在的块中寻找其声明,只考虑在名字使用之前出现的声明
    2. 没找到就继续查找外层作用域
    3. 如果最终没找到匹配项,则报错
  • 定义在类内部时: 作用域内名称查找规则

    1. 首先,在成员你函数内查找声明,只有在函数使用之前出现的声明才被考虑
    2. 如果在成员你函数内没找到,则在类内继续查找,这时类的所有成员都可以被考虑
    3. 如果类内没有找到,在成员函数定义之前的作用域内继续查找
  • 如果就是需要外层作用域中的名字,可以显示的通过作用域运算符来访问

    void Screen::dummy_fcn(pos height)
    {
    	corsur = width * ::height;	//显示访问外层全局作用域中变量
    }
    
  • 7.34 , 出现错误: unknown type pos , pos类型定义在类后面所以对类是不可见的

  • 7.35 , 为什么 Type::Exercise::setVal(Type param) // 返回值类型使用的是类外的定义

    typedef string Type;
    Type initVal(); // use `string`
    class Exercise {
    public:
        typedef double Type;
        Type setVal(Type); // use `double`
        Type initVal(); // use `double`
    private:
        int val;
    };
    // 因为函数定义是在类外面进行的,
    // 所以会先从当前 块 中前面出现的声明中找,还没进到类内
    Type Exercise::setVal(Type parm) {  // first is `string`, second is `double`
        val = parm + initVal();     // Exercise::initVal()
        return val;
    }
    

66.构造函数

构造函数初始化,默认构造函数

使用构造函数初始值还是构造函数中赋值操作依赖于数据类型的类型,有的成员必须被初始化

两种方式区别在于:

构造函数初始值: 直接初始化成员

  • 出现在初始化列表的前后关系不影响初始化顺序, 顺序与成员定义的顺序一致

    所以尽量避免使用某些成员初始化其他成员

构造函数中赋值操作 :初始化再赋值

// 比如当成员变量中包含 const类型的变量时,赋值操作就会报错,必须使用构造函数初始值
class ConstRef{
public:
    ConstRef(int Input);
private:
	const int const_i;	//常量
	int i;
	int &reference_i;	//引用
}

// 错误 : const_i和reference_i 不能被赋值,必须初始化
ConstRef::ConstRef(int Input)
{
	i = Input;
	const_i = Input;	//报错
	reference_i = Input;	//报错
}
// 正确: 显示地初始化和引用和const  成员变量
ConstRef::ConstRef(int Input):
i(Input),const_i(Input),reference_i(Input) {}
  • 7.36, 因为狗在函数初始化顺序是成员变量定义的顺序,所以构造函数初始化列表中尝试使用了未初始化的变量base, 应该把两个变量定义改为 int base,rem;
  • 7.38, Sales_data(std::istream &is = std::cin) {read(is,*this); } // 默认参数为std::cin
委托构造函数

使用构造函数初始化原理, 把构造函数作为初始化列表.

委托构造函数的构造函数执行顺序跟构造函数初始化类似, 先执行初始化列表中的顺序,再执行构造函数里的内容.

注意: 默认构造函数定义时不应该多带()

class Tree {
public:
    Tree(std::string No ,std::string name,std::string type)
        :TreeNo(No),TreeName(name),TreeType(type)
    {
        printf("normal Constructor called.\n");
    }
    // 下面三个为委托构造函数
    Tree():Tree("","",""){
        printf("default Constructor called.\n");
    }
    Tree(std::string Name) :Tree(0, Name, "") {
        printf("name Constructor called.\n");
    }
    Tree(std::istream& is) :Tree() {
        printf("istream Constructor called.\n");
        is >> TreeNo >> TreeName >> TreeType;
    }
    
private:
    std::string TreeNo;
    std::string TreeName;
    std::string TreeType;
};

int main()
{
    // 默认构造函数定义时不应该多带()
    Tree A1;	// Tree A1(); 这样定义的是一个函数而非对象
    std::cout << "----" << std::endl;
    Tree A2("白杨树");
    std::cout << "----" << std::endl;
    Tree A3(std::cin);
    return 0;
}
  • 默认构造函数: 当没有提供初始值设定项的情况下使用的构造函数, 另类说法是为其参数提供默认参数的构造函数也定义为默认构造函数.

  • 只有在类没有显示定义任何构造函数时,编译器才会隐式地为我们定义默认构造函数

  • 7.49 三种combine的不同声明方式的错误.

Sales_data& Sales_data::combine(const Sales_data &rhs)
{
    units_sold += rhs.units_sold;
    revenue += rhs.revenue;
    return *this;
}
Sales_data &combine(Sales_data);//正确
Sales_data &combine(Sale_data&);//错误,因为参数不是const类型,所以不能传递临时变量,如果是const 类型参数就能传递临时变量,string类型会隐式转换为Sales_date类型
Sales_data &combine(const Sales_data&) const;//错误, const放在变量声明尾部会禁止数据产生改变,这与combine的功能定义不符
  • 7.51 vector将其单参数的构造函数定义成explicit,而string则不是,原因可能是什么
//比如声明函数
int getSize(const std::vector<int>&);
//调用时
getSize(34);	//会有多重意义,或意义不明
//string就很少出现这种问题
void callSomeone(std::string);
//调用时
callSomeone("zhangsan");
聚合类

定义:

  1. 所有成员都是public的
  2. 没有定义任何构造函数
  3. 没有类内初始值
  4. 没有基类,也没有virtual函数

例子:

struct Data{
 	int val;
    string ss;
}

初始化

Data val1 = {0,"zhangsan"};
Data val2 = {"lisi",1};//错误
字面值常量类

定义: 就是说定义后必须全是以知的值

  1. 数据成员都必须是字面值类型(constexpr),他们是隐式的cosnt
  2. 类必须至少含有一个constexpr构造函数
  3. 如果数据成员中有别的类, 则内置类型成员的初始值必须是一条常量表达式,或者成员属于某种类型,则初始值必须使用成员自己的constexpr构造函数
  4. 类必须使用析构函数的默认定义,该成员负责销毁类的对象

67.类的静态成员

静态成员定义: 类需要与它的一些成员与类本身直接相关, 而不是与类的各个对象保持关联.

应用场景: 当类有多个实例时,能统一修改每个实例中的值

特点: 必须在类外面初始化

  1. 不能被声明为const
  2. 不能使用类内this指针访问
  3. 不能在static函数体内使用this指针
静态成员的类内初始化

静态成员通常不能在类内初始化,但可以为const或constexpr进行初始化

在类内部提供了一个初始值,则成员的定义不能再指定一个初始值了

即使一个常量静态数据成员在类内部被初始化,通常情况下也应该在类外部定义一下该成员(原因未知,好像是传递给函数时会出现问题,但我没测出来)

class Account{
private:
    static constexpr int period = 30;	//period是常量表达式
    double daily_tbl[period];
}

// 一个不带初始值的静态成员的定义
constexpr int Account::period;	//初始值在类的定义内提供

class Example {
public:
    static constexpr double rate = 6.4;
    static const int vecSize = 20;
    static vector<double> vec;
};
constexpr double Example::rate;
//const int Example::vecSize;	//就算删掉这句也不会编译出错,但如果这个值在
vector<double> Example::vec(Example::vecSize);
静态成员和普通成员的差别
  1. 静态成员在类内可以是不完全类型(只清楚类型,但不清楚包含哪些成员)

    普通成员不能以不完全类型包含在类内,只能声明成指针或引用

class Bar{
private:
    static Bar mem1;	// 正确,静态成员可以是不完全类型
    Bar mem2;			// 错误,数据成员必须是完全类型
    Bar *mem3;			// 正确,指针或引用可以是不完全类型
}
  1. 静态成员可以作为默认参数, 普通成员不行

IO

68.IO条件状态

  • io对象无拷贝或赋值,不能对流对象赋值和拷贝流对象
  • io对象的条件状态
strm::iostatestrm是一种io类型,iostate是一种机器相关的类型,提供了表达条件状态完整功能
strm::badbitstrm::badbit用来支出流以崩溃,无法再使用流
strm:failbit用来支出一个io操作失败了,可以被修正,可以继续使用
strm::eofbit用来指出流到达了文件结尾
strm::goodbit用来指出流未处于错误状态,此值保证为0
s.eof()若流s的eofbit置位,则返回true
s.fail()若流s 的failbit或badbit 置为, 则返回true
s.bad()若流s 的badbit置为,则返回true
s.good()若流s 处于有效状态, 则返回true
s.clear()将流s 中所有状态位复位,将流的状态设置为有效,返回void
s.clear(flag)根据给定的flag标志位,将流s 中对应条件状态位复位,flag的类型为strm::iostate ,返回void
例如:复位failbit和badbid,保持其他标志位不变
cin.clear(cin.rdstate() & ~cin.failbit & ~cin.badbit);
s.setstate(flags)根据给定的flag标志位,将流s 中对应条件状态置位,flag的类型为strm::iostate ,返回void
s.rdstate()返回流s的当前条件转改,返回值类型为strm::iostate

一个流一旦发生错误,后续的io操作都会失败,只有当一个流处于无错误状态时才可以向流读取或写入数据.

//确定流状态的最简单方法, 当做条件来使用.
while(cin>>word)
    ...;// ok,读操作成功;
//while循环检查>>表达式返回流的状态.
  • 习题: 8.3 什么情况下,while(cin>>i) 会终止?

输入错误状态,比如eofbit,failbit,badbit 会终止输入流

cin 操作符是根据后面变量的类型读取数据。

输入结束条件 :遇到Enter、Space、Tab键。

[C++输入cin详解]:https://www.cnblogs.com/A-Song/archive/2012/01/29/2331204.html

69.输出缓冲

缓冲区满时,需要刷新缓冲才能继续写入数据到缓冲区.

在每个输出操作后,可以用操纵符 unitbuf设置流的内部状态,清空缓冲区

  • std::cerr是设置unitbuf的,因此cerr的内容都是立即刷新缓冲区的.
  • 一个输出流可能被关联到另一个流,关联到的流的缓冲区会被刷新.
  • 当程序崩溃,输出缓冲区不会被刷新,输出数据可能会停留在输出缓冲区中等待打印
endl输出换行,并刷新缓冲区
flush不附加任何额外字符,刷新缓冲区
ends输出一个空字符, 然后刷新缓冲区
  • unitbuf操作符,nounitbuf操作符
// 每次输出操作后都刷新缓冲区.
cout<<unitbuf;	
// 回到正常的缓冲方式
cout<<nounitbuf;
  • 关联输入和输出流 tie

    istream和ostream之间可以相互关联,isteam和istream之间也可以,ostream同理.

    如果本对象当前关联到一个输出流,则返回的就是指向这个流的指针.

// old_tie指向当前关联到cin的流(如果有的话)
ostream *old_tie = std::cin.tie(&outstream);//关联输入流到自定义的流

std::cin.tie(nullptr);// cin不再与其他流关联,cin应该关联到cout
  • 每个流同时最多关联到一个流,多个流可以同时关联到同一个ostream

70.文件输入输出特有操作

fstream fstrm;创建一个未绑定的文件流,fstream是头文件fstream中定义的一个类型
fstream fstrm(s);创建一个fstream,并打开名为s的文件,s可以是string类型,或者指向C风格字符串的指针,这些构造都是explict的,默认文件模式mode依赖于fstream的类型
fstream fstrm(s,mode);与前一个构造函数类似,但按指定mode打开文件
fstrm.open(s);打开名为s的文件,并将文件与fstrm绑定,s可以是string也可以是c风格字符串指针,默认文件mode依赖于fstream的类型,返回void
fstrm.close();关闭与fstrm绑定的文件,返回void
fstrm.is_open();返回一个bool值,支出与fstrm关联的文件是否成功打开且尚未关闭
  • 文件模式(mode)

    fstream默认以 in,out模式打开.

    保留被ofstream打开的文件中已有数据的唯一方法时指定app或in模式.

in读方式打开, ifstream的默认方式
out写方式打开, ofstream的默认方式 , 此模式打开文件会丢失已有数据,除非指定app模式
app每次操作前均定位到文件末尾, trunc没被设置就能用app模式, 设定app模式后会自动以out模式打开文件
ate打开文件后立即定位到文件末尾, 可以同于任何类型文件流对象, 且可以与其他任何模式组合使用
trunc截断文件(覆盖), out模式下才可设定trunc
binary以二进制方式进行io
  • 当fstream对象被销毁时,close会自动被调用.
// 程序输入参数列表
for(auto p = argv + 1; p != argv+argc ; ++p);
  • 习题 8.5 :
void ReadFileToVec(const string& filename, std::vector<std::string>& vec)
{
    ifstream ifs(filename);
    if (ifs)
    {
        std::string buf;
        //while(std::getline(ifs,buf))  //按行读取,即一整行字符串都读取,包含控股个
        while (ifs >> buf)              //读取单词,即读取空格以外的字符串
            vec.push_back(buf);
    }
}

71.string流 sstringstream

  • istringstream

应用场景: 对整行文本进行处理,处理行内单个单词

跟while(iss >> buf) ; 配合使用达到目的

  • ostringstream
struct PersonInfo{
    string name;
    vector<std::string> phones;
}
for(const auto& entry : people)
{
    ostringstream formatted,badNums;
    for(const auto& nums:entry.phones)
    {
        if(!valid(nums))
        {
            badNums<<" "<<nums;	//将数的字符串形式存入badNums
        }else{
            formatted<<" "<<format(nums);
        }
    }
}

习题8.14: 为什么entry和nums定义为const auto&?

  1. 因为它们的类型都是类类型,而不会内置类型, 所以使用引用会更有效率
  2. 输出数据不需要修改,所以用const

顺序容器

  • vector : 可变大小数组,快速随机访问,在尾部之外的位置插入或删除元素可能很慢
  • deque: 双端队列,支持快速随机访问, 在头尾位置插入/删除速度很快
  • list : 双向链表, 支持双向顺序访问,在list中任何位置进行插入/删除操作速度都很快
  • forward_list: 单向链表, 只支持顺序访问, 在链表任何位置进行插入/删除操作速度都很快
  • array: 固定大小数组, 支持快速随机访问, 不能添加或删除元素
  • string: 与vector容器类似 , 随机访问快,在尾部插入/删除快

选择容器基本原则:

  1. 考虑空间额外开销,不要用list 或 forward_list
  2. 要求随机访问, 使用vector或deque
  3. 只会在头尾插入/删除操作, 使用deque
  4. 只在读取输入时需要插入元素, 随后随机访问元素 : 必须要在中间位置插入元素,考虑在输入阶段使用list, 输入完成将list 中内容拷贝到vector中.

72.容器保存类型的限制

顺序容器: 只能保存有默认构造函数的的对象, 或者需要传入元素初始化器

vector<noDefault> V1(10,initFunc);	// 创建大小为10的容器,并提供初始化器

不常见的:

  • size_type; //无符号整型类型,足够保存容器类型最大可能大小

  • difference_type; //带符号整型类型,足够保存两个迭代器之间的距离

  • value_type; //元素类型

  • reference; // 元素左值类型, 与value_type& 含义相同

  • const_reference; // 元素的const左值类型, 与const value_type& 含义相同

  • C c(a,b,c…); // 列表初始化

  • c.emplace(inits) ; //使用inits构造c中的一个元素

  • 反向容器的额外成员(不支持forward_list)

    reverse_iterator:按逆序寻址迭代器

    const_reverse_iterator:不能修改元素的逆序迭代器

    c.rbegin; c.rend() : 反向指向c的尾元素和首元素之前位置的迭代器

    c.crbegin; c.crend() : 返回const_reverse_iterator

73.迭代器

迭代器限制
  • 如果begin与end相等,则范围为空
  • 如果begin与end不等,则范围至少包含一个元素,且begin和end构成一个合法的迭代器范围
//左闭合区间
std::vector<int> a;
// [a.begin(),a.end())    [a.fisrt(),a.last())
end() 和 last() 指向的是最后一个元素的下一个位置
  • 不要尝试比较 迭代器
while(iter1 < iter2) {} // 错误
while(iter1 != iter2) {} // 这样判断迭代器是否到底

74.容器初始化

初始化容器时插入到容器的对象是拷贝后的对象,而不是对象本身

容器中的元素与提供值的对象之间没有联系, 容器中的元素改变不会影响原始对象

C c;		// 容器默认构造函数
C c1(c2);	// c1初始化为c2的拷贝,
C c1 = c2;	// 相同容器类型,且保存的元素类型相同(array的话大小也得相同)
C c(a,b,c,d...);	// 初始化为列表初始化中元素的拷贝,元素类型必须与C的元素类型
C c = {a,b,c,d...};	// 相同
C c(b,e);	// c初始化为 迭代器b和e指定范围中的元素的拷贝(不适用array)
C seq(n);	// seq包含n个元素,这些元素进行了初始化,初始化时使用的构造函数是				// explicit的
C seq(n,t);	// seq包含n个初始化为值 t 的元素
// array的初始化
array<int,42> C;	//必须指定参数类型和大小

75.容器赋值和swap,assign替换

c1 = c2;		//将c1中的元素替换为c2中元素的拷贝, c1 c2是相同的类型
c = {a,b,c,d..};//c1中的元素替换为初始化列表中的元素的拷贝	
swap(c1,c2);	//交换c1和c2中的元素,类型必须相同
c1.swap(c2);	//通常swap比,c2向c1拷贝元素快得多
// assign操作不适用于array
seq.assign(b,e);//将seq中的元素替换为迭代器b和e所表示的范围中的元素
				//迭代器b和,e能指向seq中的元素
seq.assign(c);	//将seq中的元素替换为初始化列表c中的元素
seq.assigin(n,t);//将seq中的元素替换为n个值为t的元素
谨慎使用swap后的迭代器,指针,引用

swap操作后原本指向容器的迭代器,引用,指针都不会失效, 但是这些元素属于不同的容器

iter在swap前指向svec[3]的元素,那么swap后他指向svec2[3]的元素,

9.14

#include <iostream>
#include <list>
#include <vector>
void practice914()
{
    std::list<char *> Alist = {"aaaa","bbbb","ccc"};
    std::vector<string> Avec;
    Avec.assign(Alist.cbegin(),Alist.cend());
    for(const auto& it:Avec)
    {
        std::cout<<it<<std::endl;
    }
}

76.容器的比较

元素类型也定义了比较运算符时才可以比较.

容器的==, < , > 运算符使用的是元素的相应运算符.

vector<int> v1 = {1,3,5,7,9,12};
vector<int> v2 = {1,3,9};
vector<int> v3 = {1,3,5,7};
vector<int> v4 = {1,3,5,7,9,12};
v1 < v2; // true, 在v2[2]的元素上 v1输了
v1 < v3; // false, 在元素数量上v3输了
v1 == v4; // true , 元素数量还是值都一样
v1 == v2;// false, v2元素数量比b1少

77.容器添加元素

向一个vector,string或deque插入元素会使所有指向容器的迭代器,引用和指针失效

  • emplace(p,args) p指迭代器, args指元素构造函数需要的参数
  • emplace_back(args)
  • emplace_front(args)

上面三个函数分别对应 insert,push_back,push_front, 差别是emplace只需要传入参数就会在容器内存中直接构造对象, 正常的push_back需要我们构造临时对象后再把对象作为参数传入.

  • lst.insert(p,t) 迭代器p前面插入元素对象t, 返回指向新元素的迭代器p
  • lst.insert(p,9,t) 迭代器p前面插入9个值为t的元素,返回指向新添加的第一个元素的迭代器
  • lst.insert(p,b,e) 迭代器p前插入,迭代器b和e范围内的元素,返回新添加第一个元素迭代器,传给insert的迭代器不能指向目标容器.即b,e不能指向lst的容器
  • lst.insert(p,il) 迭代器p前插入, il(花括号包围的元素值列表) {t1,t2,t3},返回指向t1的迭代器

注意:lst.insert(p,b,e) b,e不能指向lst的容器

将元素insert到vector,deque和string中的任何位置都很耗时

9.20

#include <iostream>
#include <deque>
#include <list>
void practice920()
{
    list<int> num_list = {1,2,3,4,5,6,7,8,9};
    deque<int> odd,even;	//奇数,偶数
    //k&0x1表示k与0x1按位与,其效果为取k的二进制中最右边的数字
    for(auto i : num_list)
    {
        (i &0x1 ? odd:even).push_back(i);
    }
    for(auto i :odd) std::cout<<i<<" ";
    std::cout<<std::endl;
    for(auto i :even) std::cout<<i<<" ";
    std::cout<<std::endl;
}

78.容器取值

c.back();	//返回c中尾元素的引用,跟迭代器不一样,这直接就是值
c.front();	//返回c中首元素的引用
c[n];		//返回下标为n的元素的引用
c.at(n);	//同上,越界会抛出 out of range

79.容器删除元素

删除deque中除首元素之外的任何元素都会使迭代器,引用,指针失效.

指向vector或string中删除的某个位置之后的迭代器会失效.

如果需要用到删除的元素,需要在删除前先保存

c.pop_back();	//删除尾部元素
c.pop_front();	//删除头部元素
c.erase(p);		//删除迭代器p指定元素,返回指向p后的元素迭代器
c.erase(b,e);	//删除迭代器b,e范围内的元素,返回指向被删除元素后一位的迭代器
c.clear();		//清空
forward_list单向链表,插入删除

用的比较少,没见过项目用这个

lst.before_begin();	// 返回指向链表首元素之前,不存在元素的位置
lst.cbefore_begin();// 同上,但是是const_iterator
lst.insert_after(p,t);	// 在迭代器p后插入t
lst.insert_after(p,n,t);// 在迭代器p后插入n个t,返回最后一个被插入元素迭代器
lst.emplace_after(p,args);	//args作为参数在迭代器p后面创建元素
lst.erase_after(p);		// 删除迭代器p后面的一个元素,返回被删除元素之后的元素迭代器
lst.erase_after(b,e);	// 删除迭代器b和e之间的元素,返回同上

80.容器大小管理

每次重新分配capacity() 会变为原来的两倍.

list<int> list1(10,42);	//10个int,值为42
list1.resize(15);		//增加5个值为0的元素到list尾部
list1.resize(25,-1);	//将10个值为-1的元素添加到list尾部
list1.resize(5);		//从list末尾删除20个元素
// shrink_to_fit 适用于 vector,string,deque
// capacity 和 reserve 适用于 vector , string
c.shrink_to_fit();	//将capacity()减少为与size()相同大小
c.capacity();		//不重新分配情况下,c可以保存多少元素
c.reserve(n);		//分配至少能容纳n个元素的内存空间
  • reserve只在n超过当前容量时才会生效,不然无事发生,不会退回空间

  • resize 只会改变容器中元素的数目,而不是容器的容量,不能用resize减少容器预留内存空间

  • shrink_to_fit 也不保证能退回内存空间

9.37

list 不连续保存元素。 array 静态地具有固定大小。

81.string的奇怪使用

  • 指定字符添加到string的方法:

    1. 来自于一个字符指针,指向字符数组
    2. 来自于一个花括号{}包围的字符列表
  • string 没提供一个翻转字符串的内置函数,但STL有,如下参考

// 反转string字符串 包装STL的reverse()  可以inline
inline void STL_Reverse(std::string& str) 
{
    reverse(str.begin(), str.end());
    // STL 反转函数 reverse() 的实现
    /*     template <class BidirectionalIterator>
     *     void reverse(BidirectionalIterator first, BidirectionalIterator last)
     *     {
     *         while ((first != last) && (first != --last))
     *             swap(*first++, *last);
     *     }
     */
}
// 仿制STL的算法的,适合string字符串反转函数
void good_Reverse(std::string &word)   
{// 效率比 C++ Primer Plus 的高一点
    size_t first, last;
    first = 0;
    last = word.size();
    while ((first != last) && (first != --last))
        std::swap(word[first++], word[last]);
}

9.43

auto replace_with(std::string& s, std::string const& oldVal, std::string const& newVal)
{
	for (auto cur = s.begin(); cur <= s.end() - oldVal.size(); )
        // 构造临时string对象与oldVal比较
		if (oldVal == std::string{ cur, cur + oldVal.size() })
            // 使用点(.)运算符,而不是每行都用分号(;)
            // 从cur位置开始删除oldVal长度的字符串.
			cur = s.erase(cur, cur + oldVal.size()),
    		// 从cur.erase返回的被删字符的下一个位置开始,插入字符
			cur = s.insert(cur, newVal.begin(), newVal.end()),
    		// 跳过插入字符的长度
			cur += newVal.size();
		else
            // 没找到oldVal ,开始下一个字符
			++cur;
}

9.44

auto replace_with(std::string &s, std::string const& oldVal, std::string const& newVal)
{
    for (size_t pos = 0; pos <= s.size() - oldVal.size();)
        // 新增了一个字符比较过程,以免每次都要构造新的string
        if (s[pos] == oldVal[0] && s.substr(pos, oldVal.size()) == oldVal)
            s.replace(pos, oldVal.size(), newVal),
            pos += newVal.size();
        else
            ++pos;
}

82.string的搜索

std::string::npos; // unsigned类型,初始值为-1, 搜索失败时返回

s.find(args);	// 查找s中args第一次出现的位置
s.rfind(args);	// 查找s中args最后一次出现的位置
s.find_first_of(args);	// 查找s中args中任何任何一个字符第一次出现的位置
s.find_last_of(args);	// 查找s中args中任何任何一个字符最后出现的位置
s.find_first_not_of(args);	// 查找s中第一个不在args中的字符出现的位置
s.find_last_not_of(args);	// 查找s中最后一个不在args中的字符出现的位置
args
    可以是 c,pos : 在s中, 从位置pos开始查找字符c.
    可以是 cp,pos,n :在s中,从位置pos开始查找指针cp指向数组的前n个字符.

83.数值转换

to_string(val);	// 一组重载函数,可以对应浮点型,整型,更大的整型参数
stoi(str,pos,b);// b为基数,pos默认为0,表示从str的第一位开始转换
stol(str,pos,b);// b为基数,long
stoul(str,pos,b);// b为基数,unsigned long
stoll(str,pos,b);// b为基数,long long
stoull(str,pos,b);// b为基数,unsigned longlong
stof(str,pos);		// float
stod(str,pos);		// double
stold(str,pos);		// long double

84.(adaptor)容器适配器 stack,queue,priority_queue

所有适配器都支持

  • size_type 一种类型,保存当前类型的最大对象大小
  • value_type 元素类型
  • container_type 实现适配器底层容器类型
  • 关系运算符 ==,!=,<,<=,>,>= 都支持
stack 头文件中
s.pop();//出栈,删除栈顶元素,不返回元素值
s.push(item);//压入栈顶部
s.emplace(args);	// 通过args,构造元素的值
s.top();		//返回栈顶元素
queue 头文件中

queue很奇怪都不删除元素…

priority_queue 则是默认基于vector实现;
queue 也可以用list或vector实现,priority_queue 也可以用deque实现;
q.pop();	//返回queue的首元素或priority_queue的最高优先级元素,但不删元素
q.front();	//返回首元素或尾元素,但不删元素
q.back();	//只适用于queue
q.top();	//只适用于priority_queue,返回最好优先级元素,但不删除该元素
q.push(item);	//queue末尾或priority_queue中恰当位置创建一个元素
q.emplace(args);//值为item,或者有args构造

practice_952

/*
使用stack处理括号表达式,把括号中的字符取出,并按需使用
*/
#include <stack>
void practice952()
{
    std::string expression("this is (perz)");
    bool bSeen = false;
    std::stack<char> stk;
    for (const auto &s : expression)
    {
        if (s == '(') {
            bSeen = true; continue;
        }    
        else if (s==')')
            bSeen = false;
        if (bSeen)
            stk.push(s);
    }
    std::string repstr;
    while (!stk.empty())
    {
        repstr += stk.top();
        stk.pop();
    }
    std::reverse(repstr.begin(), repstr.end());
    expression.replace(expression.find("(") + 1, repstr.size(), repstr);
    std::cout << expression << std::endl;
}

algorithm标准库泛型算法

泛型算法不会执行容器的操作,只会运行于迭代器之上, 所以算法永远不会改变容器的大小,不会直接添加或删除元素

85.find,find_if

  • find:返回第一个等于val的迭代器,没找到就返回std::end(alist)即第二个参数
std::vector<int> vec(5,53,1,2,44,42);
int val  = 42;
auto result = std::find(vec.begin(),vec.end(),val);
int alist[] = {27,210,12,47,109,83};
auto result = std::find(alist,alist+4,val);//在下标0-3范围内查找
  • find_if

可以传递2个迭代器范围参数和1个条件函数(lambda表达式或函数)

// 返回一个迭代器,指向第一个满足函数条件的元素
int sz = 4;	// 字符个数大于4个为条件
std::vector<string> words = { "fot","jump","over","quick","red","red","slow","the","the","turle" };
auto wc = std::find_if(words.begin(),words.end(),
                      [sz](const std::string& a){
                          return a.size()>sz;
                      });

86.begin,end

int alist[] = {27,210,12,47,109,83};
int val = 83;
// 返回第一个等于val的迭代器,没找到就返回std::end(alist)即第二个参数
int *result = std::find(std::begin(alist),std::end(alist),val);

87.count

// count返回值在给定序列中出现的次数
std::vector<int> v = { 1, 2, 3, 4, 5, 6, 6, 6, 2 };
std::cout << "ex 10.01: "<< std::count(v.cbegin(), v.cend(), 6) << std::endl;

88.accumulate

前两个参数确定范围, 第三个参数确定返回值的类型

// 容器元素的求和,并设置初值
int sum = accumulate(vec.begin(),vec.end(),0);	//sum初值为0
// string的情况: 全部字符连接起来
std::string = accumulate(v.cbegin(),v.cend(),string(""));
// std::vector<double> vd = { 1.1, 0.5, 3.3 };
std::cout   << "ex 10.04: "
    << std::accumulate(vd.cbegin(), vd.cend(), 0<< std::endl;
    //因为这里传的是0,所以返回值类型为int,所以计算出来的结果是4,而不是4.9

89.fill,fill_n,back_insert

  • fill向容器中初始化元素
fill(vec.begin(),vec.begin()+vec.size()/2 , 10);	//一半序列初始化为10
  • fill_n ,向容器中插入某个个数的元素
std::list<int> lst;
// 第一个参数为插入开始位置的迭代器,第二个参数插入个数,第三个参数要插入的值
fill_n(lst.begin(),lst.size(),0);	//从头到尾
  • 插入迭代器,back_insert,

一种保证迭代器指向的容器有足够空间来容纳输出数据的方法

接收一个指向容器的引用,给引用赋值就能插入到容器中

需要 #include 头文件

vector<int> ve;
auto it = back_insert(ve);
*it = 42;	// vec中插入42

90.replace,replace_copy

前两元素为容器范围,第三个元素为要替换的元素,替换成第四个元素

// 替换list中的0为42
replace(list.begin(),list.end(),0,42);
// 带copy表示要拷贝一份出来
std::list<int> ilst = {0,1,0,5,1,4,3};
std::list<int> ilstc;
replace_copy(list.begin(),list.end(),back_insert(ilstc),0,99);

practice_10.7

template<typename Sequence>
void print(Sequence const& seq)
{
    for (const auto& i : seq)
        cout << i << " ";
    cout << endl;
}
// (a)
vector<int> vec;
list<int> lst;
int i;
while (cin >> i)
    lst.push_back(i);
//copy(lst.cbegin(), lst.cend(), vec);//这里因为vec不能自动添加元素会报错.
copy(lst.cbegin(), lst.cend(), back_inserter(vec));//使用back_insert解决

// (b)
vector<int> v;
//v.reserve(10);
//编译没有错误,但是v.size()依旧为0,虽然v.capacity()是10,但这是两码事,插入元素时要看的是size(),而不是capacity().
v.resize(10);	
//第一钟方法.直接改变容器大小.
//fill_n(v.begin(), 10, 0);
fill_n(std::back_inserter(v), 10, 0);
//第二种方法,使用back_insert自动插入元素防止容器大小不足

91.容器排序和消除重复

  • std::sort 排序

std::sort 默认从小到大顺序排序容器或数组中的内容,也可以指定第三个参数自定义排序方法.

  • std::unique 消除重复项(重新排序)

unique操作并不会自动删除容器中的元素, 只会把容器中重复的项都移动到后面的位置,而且end_unique指向的是容器中最后一个不重复元素后的值(所以不能确定到底是哪个重复项),有的的重复项会变成了""(空字符串)留在容器end_unique后面,所以容器种元素的个数依旧一样.

需要真正删除是要用到容器自身的erase函数删除

c++ primer中是先用sort排序后再用unique进行排序的.

std::vector<string> words = {fot,jump,over,quick,red,red,slow,the,the,turle};
// 先用sort排序
sort(words.begin(),words.end());
// 使用unique重新排序
auto end_unique = unique(words.begin(),words.end());
// 使用vs2019操作后结果是
// fot jump over quick red slow the turle the
// 其中end_unique指向最后一个the,但不知为何red没了一个,还说unique不会删东西?
// 断点调试发现unique后the后面的字符串为""空字符串,所以words元素个数没变
// 使用向量操作erase删除重复单词,end_unique指向重复项的第一个位置,因为重排序了
words.erase(end_unique,words.end());	
//下面是简化版,重排序+删除
//words.erase(unique(words.begin(),words.end()), words.end());

如果不用sort直接unique排序会怎样呢.

std::vector<string> words = { "fot","jump","over","quick","red","red","slow","the","the","turle" };
// 使用unique重新排序
auto end_unique = unique(words.begin(),words.end());
// 结果:
// fot jump over quick red slow the turle the
// 结果中end_unique指向最后一个the,但的确没有sort也行的,但是还是会留一个重复项在最后
// 即时容器中无重复项erase也不会有问题,因为end_unique总会指向最后一个不重复项的下一个位置,erase空范围不会报错.
words.erase(end_unique,words.end());	//这句还是得带着

practice_10.10

algorithm中的算法不改变容器大小的原因是什么?

算法操作的是迭代器而不是容器本身,所以算法不能直接删除容器元素

92.sort 和 partition

  • stable_sort ,跟sort用法类似, 但保持等长元素间的字典顺序

  • std::partition , 必须传递一个自定义函数(谓词)作为参数, 作用是按照谓词要求对容器进行划分, 谓词返回true的元素排到前面,false的排到后面, 返回值为指向最后一个使谓词为false的元素之后位置.

  • stable_partition, 划分后序列中维持原有元素序列

bool mypredicate(const std::string& s)
{
	return s.size() >= 5;
}
void test_partition()
{
	auto v = std::vector<std::string>{ "a", "as", "aasss", "aaaaassaa", "aaaaaabba", "aaa" };
	auto pivot = std::partition(v.begin(), v.end(), mypredicate);

	for (auto it = v.cbegin(); it != pivot; ++it)
		std::cout << *it << " ";
	std::cout << std::endl;
    //利用返回的结果迭代器,输出条件为true的元素
	//aaaaaabba aaaaassaa aasss
    
    for (auto it = v.cbegin(); it != v.cend(); ++it)
		std::cout << *it << " ";
	//进行partition后的vector元素排序如下,可见还是根据长度排序过的
	//aaaaaabba aaaaassaa aasss as a aaa
}

93.for_each和transform

三个参数,前两个参数表示迭代器范围,第三个参数表示对此范围进行的操作函数,但函数可接受参数必须为1个.

// 输出每个words中的元素
for_each(words.begin(),words.end(),[](const std::string& a){
    std::cout<<a<<" ";
});
std::cout<<std::endl;

四个参数,前三个迭代器和一个可调用对象, 前两个迭代器表示输入序列,第三个迭代器表示目标位置, transform对序列中每个元素调用可调用对象. 将结果写入到目标位置

//当输入迭代器和目标迭代器相同时,transform将输入序列中每个元素替换为
//可调用对象操作该元素得到的结果,就跟for_each差不多了
transform(vi.begin(),vi.end(),vi.begin(),[](int i) -> int {
    return i<0?-i:i;
});

94.lambda表达式

lambda概念

可以理解为未命名的内联函数,

  • 必须尾置返回来指定返回类型
  • 可以忽略参数列表和返回类型, 但必须永远包含捕获列表和函数体
  • 调用方式跟普通函数一样
[capturelist](parameter list) -> return type{function body}
/* capture list(捕获列表):lambda所在函数中定义的局部变量列表,通常为空表示不适用它所在函数中的任何局部变量 */
// return type: 返回类型
// parameter list: 参数列表
// function body: 函数体
//例子:
auto f = []{return 42;};//没指定返回类型就根据表达式推算,返回类型void
auto f2 = []{return;};	//这样就返回void了,或者没有return
// 给sort传递函数时如下:
stable_sort(words.begin(),words.end(),
           [](const std::string& a,const std:string& b)
            {return a.size()<b.size();});
使用lambda捕获列表
  • 捕获列表只能用于局部非static变量
  • 可以直接使用局部static变量和它所在函数之外声明的非static变量
void test_lambda()
{
    // 字符个数大于4个为条件
	int sz = 4;	
	std::vector<string> words = { "fox","jump","over","quick","red","red","slow","the","the","turle" };
    // 以字符数量从小到大排序
    std::stable_sort(words.begin(), words.end(), [](const std::string& a, const std::string& b) {
        return a.size() < b.size(); });
    // 查找字符串数量大于4的字符迭代器
	auto wc = std::find_if(words.begin(), words.end(),
		[sz](const std::string& a) {
			return a.size() > sz;
		});
    // 输出到这个为尾部的迭代器
    auto count = words.end() - wc;  
    // 表示count局部变量会被使用
    auto make_plural = [count](const std::string& wd, const std::string& b) {
        return count > 1 ? wd + b : wd;
    };
    std::cout << count<<" "<<make_plural("word","s")<<" of length "<<sz <<" or longer" << std::endl;
}
值捕获和引用捕获

值捕获: 捕获局部变量的值是在lambda创建时拷贝的.

引用捕获: 捕获变量本身,随着变量改变,捕获的值也会跟着改变, 所以一般少用, 容易出错,除非遇到只能用引用才能捕获的对象(如:os) , 捕获指针或迭代器时必须保证变量有意义

  • 显式

    [sz] , 采用值捕获,显示指明变量

    [&sz], 采用引用捕获, 显示指明变量

  • 隐式

    [=] , 代表使用隐式捕获,采用值捕获

    [&] , 代表使用隐式捕获,采用引用捕获

  • 显示隐式混合使用

    显示捕获的变量必须使用与隐式捕获不同的方式(一方用了值捕获,另一方就得用引用捕获)

12
[&,identifier_list]identifier_list 是一个逗号分隔的列表,包含0个或多个来自函数的变量,采用值捕获方式, 任何隐式捕获的变量都采用引用方式捕获,identifier_list中的名字前面不能使用&
[=,identifier_list]identifier_list中的变量都采用引用捕获方式,任何隐式捕获的变量都采用值捕获,identifier_list中的名字不能包含this,且这些名字之前必须使用&
可变lambda

通过测试得知: 值捕获的变量值不能在lambda函数中产生变化.

通过引用捕获捕获到的变量要看变量本身是否是const类型的,如果是就改变不了.

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ulr45KDg-1635957619030)(C:\Users\gg\Desktop\看看\image-20210923230414756.png)]

void test_lambda2()
{
    size_t v1 = 42;
    // 添加mutable后可以改变捕获的值
    auto f = [v1]() mutable {return ++v1; };
    v1 = 0;
    auto j = f();
    std::cout << j << std::endl;
}
指定lambda返回类型

lambda函数体内包含return之外的任何语句,则编译器假定lambda返回void.

// 如果不指定返回类型就会报错,不能腿短lambda返回类型,必须加上->int
auto f1 = [](int i) -> int {
    if(i<0)		// 包含if语句,所以不指定返回类型就会报错
        return -i;
    else
        return i;
};
// 这个版本的if,编译器能推断成功
auto f2 = [](int i) {
    return i<0?-i:i;	// 因为这是表达式,不算语句
};

95.标准库 bind ,参数绑定

能够接受一个可调用对象,生成一个新的可调用对象

//头文件
#include <functional>
auto newfunc = std::bind(func1,arg_list);
// arg_list是逗号分隔的参数列表,对应func1的参数
// 当我们调用newfunc时,会调用func1,并传递arg_list中的参数
placeholders

使用bind时除了绑定的参数之外, 可变的参数需要传递给新的函数, 这时使用placeholders.

_n 定义在placeholders命名空间中

using namespace std::placeholders; //即可使用placeholders的所有名字
#include<functional>
using namespace std::placeholders;
bool isShorter(const std::string& a, const std::string& b)
{
	return a.size() < b.size();
}
auto newfunc = std::bind(isShorter, _1, a);
std::string b("assco");
std::cout << newfunc(b) << std::endl;

auto newfunc = std::bind(isShorter, _1, a);

其中的_1表示传递给newfunc的第一个参数,依次类推…

绑定引用参数std::ref

bind不能直接绑定引用参数, 原因: bind拷贝其参数,而有些参数不能拷贝.

使用 std::ref(os)函数返回一个对象,此对象包含给定的引用,也是可以拷贝的

//也是在头文件<functional>中
std::vector<string> words = { "fox","jump","over","quick","red","red","slow","the","the","turle" };

//for_each(words.begin(),words.end(),[&os,c](const string& s){os<<s<<c;});
std::ostream &print(std::ostream &os,const string& s, char c)
{
    return os<<s<<c;
}

for_each(words.begin(),words.end(),bind(print,std::ref(os),_1,' '));

std::cref() ; 生成一个保存const引用的类.

96.插入迭代器

接受一个容器,生成一个迭代器,实现向给定容器添加元素功能.

  • back_inserter,只有支持push_back的情况下才能用
  • front_inserter,只有支持push_front的情况下才能用
  • inserter
std::list<int> c;
int val  = 2;
auto it = inserter(c,val);	// it指向新加入的元素
it++;						// 让it指向原来的元素

97.copy, unique_copy

copy的第三个参数属于输出迭代器.

  • unique_copy

    接受三个迭代器, 前两个参数表示序列, 第三个参数表示要拷贝到指定的迭代器上.

  • copy

    同上

void test_copy2()
{
    std::vector<int> vec{ 1, 1, 3, 3, 5, 5, 7, 7, 9 };
    std::list<int> lst;
    
    std::unique_copy(vec.begin(), vec.end(), back_inserter(lst));
    for (auto i : lst)
        std::cout << i << " ";
    std::cout << std::endl;
    
    //practice_1028
   	std::<list> lst2;
    copy(vec.cbegin(),vec.cend(),inserter(lst2,lst2.begin()));
    //inserter(lst2,lst2.begin()) 返回值为lst2的初始位置
    //copy就从lst2的初始位置开始迭代赋值,所以顺序还是不变的1,1,3,3......
}

98.iostream 迭代器

仅支持 递增(iter++,iter–) , 解引用(*iter) , 赋值(iter = in)

istream_iterator 读取输入流

  • 需要指定读写的对象类型等等…
  • istream 尾后迭代器,用于辨别是否到了数据流尾部.
istream_iterator<int> int_it(cin);  //默认初始化, 从cin读取int

//ifstream in("afile");
//istream_iterator<string> str_it(in);//从文件读取字符串
istream_iterator<int> eof;	// istream尾后迭代器
vector<int> vec;
while(int_it != eof)
    vec.push_back(*int_it++);
  • 从迭代器范围构造vec,体现istream_iterator的优势
istream_iterator<int> in_iter(cin),eof;
vector<int> vec(in_iter,eof);	//从迭代器范围构造vec

cout<< std::accumulate(in_iter,eof,0)<<endl;//<algorithm>头文件中的求和

ostream_iterator 输出流

ostream_iterator<T> out(os);	//out将类型为T的值写入到输出流os中
ostream_iterator<T> out(os,d);	//同上,但是每个值后面都输出一个d,d指向一个空字符结尾的字符数组
out = val;	//用<<运算符[将val写入到out所绑定的osteram中],val的类型必须与out可写的类型兼容

例子:

ostream_iterator对象在碰到运算符*或++时不做任何事情,因此忽略它对我们的程序没有影响

vector<int> vec;
ostream_iterator<int> out_iter(cout," ");
for(auto e:vec)
    *out_iter++ = e;//每次向out_iter赋值时,写操作就会被提交.
	//out_ier = e;	//相同效果
cout<< endl;
//此程序将vec中的每个元素写到cout,每个元素后面加一个空格

//下面程序跟上面程序效果相同
copy(vec.begin(),vec.end(),out_iter);
cout<<endl;

99.反向迭代器reverse_iterator

  • base 返回普通迭代器

反应左闭合区间:

[line.crbegin(),rcomma) 和 [rcomma.base(),line.cend() ) 指向 line中相同元素范围.

rcomma和rcomma.base()必须生成相邻位置,而不是相同位置.

std::vector<string> line = ["FIRST,MIDDLE,LAST"];
// 当我们要输出LAST
auto rcomma = find(line.crbegin(),line.crend(),',');//反向查找','
cout<<string(line.crbegin(),rcomma)<<endl;//错误,结果: TASL ,不是我们想要的
cout<<string(rcomma.base(),line.cend())<<endl;//正确 

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s8WoIshX-1635957619049)(C:\Users\gg\Desktop\看看\image-20211007000726123.png)]

100.list_特定容易算法(merge,unique,remove)

会改变容器,remove会删除指定元素,unique会删除第二个和后续重复的元素

merge和splice会销毁其参数, 而通用版本merge将合并的序列写到一个给定的目的迭代器,输入序列不变

lst.merge(lst2);//lst2的元素合并到lst,lst和lst2必须都是有序的
lst.merge(lst2,comp);//comp函数返回结果为比较结果,进行合并

lst.remove(val);//调用erase删除掉跟val相同值的元素
lst.remove_if(pred);//删除满足pred函数返回true的元素

lst.reverse();//翻转lst中的元素顺序

lst.sort();	//使用 < 运算符 比较操作排序元素
lst.sort(comp);//使用 comp函数 比较操作排序元素

lst.unique();//调用erase删除同一个值的连续拷贝
lst.unique(pred);//删除满足pred函数返回true的元素相同元素

lst.splice(args);		//p之前的位置
flst.splice_after(args);//p之后的位置
{p,lst2}//p是指向lst中元素的迭代器, lst2不能和lst同一个链表
		//将lst2中所有元素移动到lst中p之前的位置或之后的位置,将元素从lst2中删除
{p,lst2,p2} //p2指向lst2中元素的位置,p2指向的元素移动到lst,flst中
			//lst和lst2可以是同一个链表
{p,lst2,begin,end}//begin,end表示lst2中的有效范围迭代器,指定范围中的元素从lst2移动到lst,flst, lst和lst2可以是同一个链表,p不能指向begin,end的范围
  • remove_if

    参数是迭代器,前两个参数表示迭代的起始位置和这个起始位置所对应的停止位置。

    最后一个参数:传入一个回调函数,如果回调函数返回为真,则将当前所指向的参数移到尾部。返回值是被移动区域的首个元素

#include <cctype>
//for ex11.4
auto strip(string& str) -> string const&
{
    for (auto& ch : str) ch = tolower(ch);
    // remove_if把标点符号都移动到了尾部,erase把尾部区域删除
    str.erase(remove_if(str.begin(), str.end(), ispunct), str.end());
    return str;
}

101.关联容器

分有序容器(xx)和无序容器(unordered_xx)

map

  • 元素是关键字-值(key-value)的一对,关键字起到索引作用

set

  • 每个元素只包含一个关键字,支持高效率关键字查询

无序集合

unordered_map用哈希函数组织map
unordered_set用哈希函数组织set
unordered_multimap哈希组织map,关键字可以重复出现
unordered_multiset哈希组织set,关键字可以重复出现

std::list is O(1) for inserts and deletions. But you may well need O(n) to find the insertion or deletion point.

std::set和std::map is O(log(n)) for inserts and deletions, it is usually implemented as a red-black tree.

std::boyer_moore_searcher

std::search的searcher重载调用的成员函数,使用该搜索器执行搜索

  • 返回值

    如果模式([pat_first, pat_last))为空,则返回make_pair(first, first)。

    否则,返回一对迭代器,指向[first, last)中的第一个和最后一个位置,其中pred定义的子序列比较为[pat_first, pat_last),否则返回make_pair(last, last)。

#include <iomanip>
#include <iostream>
#include <algorithm>
#include <functional>
#include <string_view>
 
int main()
{
    // The C++17 overload demo:
    constexpr std::string_view haystack =
    "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed "
    "do eiusmod tempor incididunt ut labore et dolore magna aliqua";
    const std::string needle {"pisci"};
 
    if (auto it = std::search(haystack.begin(), haystack.end(),
   std::boyer_moore_searcher(needle.begin(), needle.end()));
        it != haystack.end() )
    {
        std::cout << "The string " << quoted(needle, '\'') << " found at offset "<< it - haystack.begin() << '\n';
    } else {
        std::cout << "The string " << std::quoted(needle) << " not found\n";
    }
}
//输出:
//The string 'pisci' found at offset 43
std::binary_search
  • 返回值

    true或false

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>

int main()
{
    std::vector<std::string> exclude = { "aa", "bb", "cc", "dd", "ee", "ff" };
    for (std::string word; std::cout << "Enter plz:\n", std::cin >> word; )
    {
        auto is_excluded = std::binary_search(exclude.cbegin(), exclude.cend(), word);
        auto reply = is_excluded ? "excluded" : "not excluded";
        std::cout << reply << std::endl;
    }

    return 0;
}

pair的使用

在头文件 中

pair<T1,T2> p;	//p是一个pair,分别为两个类型T1,T2进行了值初始化
pair<T1,T2> p(v1,v2); //用v1,v2初始化T1,T2
pair<T1,T2> p = {v1,v2};
std::make_pair(v1,v2);//返回一个用v1,v2初始化的pair,pair的类型从v1和v2的类型判断
p.first; // 返回p的名为first的(公有)数据成员
p.second;// 返回p的名为seconde的(公有)数据成员

p1 relop p2; //关系运算符(>,<,<=,>=)按字典序定义; 当p1.first< p2.first 或 !(p2.first < p1.first) && p1.second < p2.second 成立时 p1 < p2为true.
p1 == p2;	// 两个元素分别相同时成立
p1 != p2;

获取关联容器的key和value的数据类型.

set<string>::value_type v1;	// v1是string
set<string>::key_type v2;	// v2是string
map<string,int>::value_type v3;	// v3是pair<const string,int>
map<string,int>::key_type v4;	// v4是string
map<string,int>::mapped_type;	// v5是一个int
// 只有map类型的容器(unordered_map,multimapdeng),才能使用mapped_type

map的数据插入

set初始化

vector<int> ivec = {1,2,4,5};
set<int> set_tmp;
//初始化列表
set_tmp.insert({1,3,4,5,6,7,8,9});
//迭代器范围
set_tmp.insert(ivec.begin(),ivec.end());

向map中添加元素

  • insert(v) , map和set使用insert时只有在元素不存在与容器内时才会插入成功, multimap和multiset总能插入成功,返回 pair<迭代器指向关键字,bool>
  • emplace(args) , 使用args来构造一个元素, 会自动构造pair<>,仅需传入值即可
std::map<int, int> tmp;
tmp.emplace(3, 23);
auto c = tmp.begin();
printf("%d,%d", c->first, c->second);
  • insert(p,v) 和 emplace(p,args)

    p为迭代器,指从那里开始搜索新元素应该存储的位置, 返回一个迭代器,指向具有v元素关键字的元素

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

路途遥远gg

帮到你了就好

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值