++chaptor1:++++++++++++++++++++++++++++++++++++++++
1 endl : 除了换行外,也可以强制缓冲区(buff) 清空
2 函数内部内置类型变量默认是不初始化
(但定义于任何函数体之外的变量会被初始化为0,例如 全局arr[100],默认为0 )
++chaptor2:++++++++++++++++++++++++++++++++++++++++
1. 基本内置类型: bool/char/short/int/long/long long/float/double/long double
char : 有些机器中char 有符号,singed char 或 unsigned char 比较准确
double : 双精度 10 位有效数字
float: 单精度浮点类型 6位有效数字(size大小和int 一样)
带符号类型 与 无符号类型 混用时候,带符号类型会自动转换成无符号类型
指针是复合类型,但是和内置类型一样,在块作用域没有初始化的话,也是不确定值;
2. 字面值常量: 10进制字面值常量是带符号数
8 / 16 进制字面可以是带符号,也可以是不带符号
0: 开头代表8 进制
0x: 开头代表16 进制
对于字面符常量前缀,后缀,最终采用能保存这个值的最小类型
前缀:
u : unicode 16字符 char16_t
U : unicode 32 字符 char32_t
L : 宽字符 wchar_t
u8 : UTF-8 char
后缀:
u/U unsigned
l/L long
ll/LL long long
f/F float
l/L long double
3.{}initialize list : 使用列表初始化 且存在初始值丢失风险时候,编译器会报错
exam :
long double ld = 341234234.23241234124;
int a{ld};
4. declaration & define 声明与定义:
extern int a; //这个才是声明,其他都是定义
extern int a = 10; //定义, extern 失去作用了
int &ra = a; // reference 必须初始化,声明可以不用
extern int &ra; //引用本身并非是一个对象
int *pa = &ld //X 错误,不同类型直接不可以转换
int *p1 = nullptr; //C11 以后才有的,可以赋值给任何类型的指针变量
int *p2 = 0, *p3 = NULL; // NULL 是预处理变量,值就是0, cstdlib中定义
void* //一种特殊的指针类型,无法知道对象的类型,仅仅是内存空间,无法访问内存空间中对象
//可以指向任意非常量的指针类型
int &*pra = &a; x //引用不是对象,所以不能定义指向引用的指针 //引用的指针
int *&rpa = &a; //但是指针是对象,所以存在对指针的引用 //指针的引用
//判断某个变量类型到底是什么,从右向左读即可,所以上面分别是指向引用的指针,某个指针的引用
const int a = 10; //只在当前文件中有效,其他文件中需要重新定义
extern const int a; //不论是声明还是定义,都加extern ,可以保证其他文件中都可以使用同一个a
//(CB) 这里还是临时初始化了一个局部变量,然后传递给引用,而不是该常量引用真指向了字面值常量42
const int &ra = 42;
const int &ra = a * 2; //这里可以给右值 或 字面常量
long double dval = 2.32;
const int &ra = dval ; //这里就是绑定一个临时量(temporary),不过不能改的临时量,意义感觉不大
exam:
long double ldval = 342345.23;
const int &rld = ldval;
cout << ldval << endl << rld << endl;
ldval = 2342.25234;
cout << ldval << endl << rld << endl;
顶层 const 与 底层 const : //主要针对指针变量,顶层 是指 指针变量自身, 底层 是指指针所指向的值
//对于一般变量,相当于只有顶层
5 typedef double wages, d1, *p; //可以一行定义多个不同的别名
6. auto : 一般会忽略顶层const , 保存底层const
//一般用于用给定的值初始化某一个新定义的变量
decltype : //只需要类型,而不需要用这个值初始化新定义的变量
//返回该变量的类型(包含顶层const 与引用在内)
EXAM:
const int ci = 0, &cj = ci;
decltype(ci) x = 0; //const int
decltype(cj) y = x; // const int & :包含顶层 const 与 引用 ,所以必须赋值
decltype(cj) z; X : 引用必须初始化
int i = 45, *p = &i, &r = i;
decltype(r + 0) b ; //这里是int, 引用作为表达式一部分,表达式结果是一个具体的值,而不是引用
decltype(*p) c; // 这里是int& , !!*是解引用操作!!,deltype 得到引用类型
decltype((i)) d ; // 加了() ,编译器会把它当成一个表达式,变量就是一种可以作为赋值语句左值的特殊表达式,所以得到的是引用类型(P63)
int arr[3] = {1, 2, 3};
auto ai(arr); // 这里 ai 是int*
decltype(arr) ba = {5,6,7}; //这里ba 是 int[3] 类型
//使用范围for 语句处理多维数组,除了最内层循环外,其他所有循环的控制变量都要是引用类型
//个人理解这里主要原因还是,非引用时候是将变量复制了一份,数组在复制时候会退化成指针的
//CB: 多维数组在使用时候,要[](这里保存着数组信息),而不是*(数组信息丢失了);
int ia[5][4] = {0};
//for(auto x : ia){ //这里auto 默认为 int* ,那么下面一层就会出错
for(const auto &x :ia) //这里避免类型被转换成指针 //p114
for(auto y : x){
}
}
7. 自定义数据类型:
C+11规定,类内初始值用于初始化数据成员
++chaptor3///////////////////////
1. #include <cctype> //C++ 中推荐用这种形式, 因为这里面保证了方法都在std namespace, 下面则没有
#include <ctype.h> //
2. for(auto x : list) //这里x 只是一个副本, 需要改变原来的值时候,可以使用 auto &x :
3. & 引用不是对象,所以在vector/Array 中不能保存引用
4. c 风格字符串 :
string 中对象可以使用 <, > 比较string
但是C风格字符串,比较的是俩个指针; 要使用 strcmp
char c1[3]= "te", c2[3]= "xm"; c1 < c2
string a1 = "test";
string b = a1 + "xx" + " "; //可以
string c = "xx" + " " + a1; X //不可以,+ 两边至少有一个是string 对象才可以
string s = "CHPATOR";
const char *str = s; // 返回const char*, 如果s在某处被改变了,有可能就会让str 失去效果
###chaptor4++++++++++++++++++++
1. - //一元运算符的优先级最高,*/ 其次,最后是+-
2. cout << i << " " << i++ << endl; //这里没有规定先后执行顺序,所以 i 有可能在i++操作后面执行;
3. 短路原则: && ||
4. 赋值运算符满足右结合律: int i1, i2; i1 = i2 = 10; //先赋值 10 给i2, 再i2 给i1
5. *iterator++ // ++ 优先级高于*
(*p).size(); //* 优先级低于.
6. ? : //条件运算符,满足右结合律, 所以右边的是左边的分支,P135
7. 位操作符:// 最好用于无符号类型,& | ~ ^ << >> , 其中<< 可能会改变符号位,是一种无定义行为
//有可能会提升原来值, unsigned char bits = 0227;
~bits => 会先被提升成int,然后按位取反; -152
8. 不同类型之间操作:先执行整型提升(char(short)- > int),然后类型还不一致时候;
一般都是小类型的对象转成较大类型(带符号类型转换成无符号类型,因为同种无符号一般比较大)
9. cast-name<type> (expression): //必须要有()
static_cast/dynamic_cast/const_cast/reinterpret_cast
static_cast : 只要不包含底层const, 都可以使用强制转换
const_cast : 去掉底层const;
//////Chaptor5//////////////////////
1. 关于exception : stdexcept
try {
throw/raise runtime_error("xxx");
} catch () {
} catch() {}
////Chaptor6////////////////////
1. 可以使用非常量初始化一个底层const 对象,
但是用一个底层const 变量初始化一个 非常量对象不行, 因为这个值是不能改变的,而两个变量指向了同一个地址
2. 数组形参有俩个注意: 1. 无法拷贝数组, 2. 使用数组是会将其转换成指针
void print(const int*);
void print(const int[]);
void print(const int[10]); //上面3 个声明,就是一样的。
所以一定想用传递数组,还想知道数组大小的话:
void print(const int*) //结尾使用特定标记
void print(const int[], size_t size); //传递大小
void print(const int *beg, const int *end); //借用std中开始指针和结尾指针后一位 begin(arr), end(arr);
传递数组的引用,可以将维度信息一起传递;
void print(int (&arr)[10]); //此时维度是类型的一部分(这里(&arr) 括号必要) //P195
//因此只能传递维度为10 的数组,限制函数的范围
void print(int (*arr)[10]); //数组中是int,
void print(int *arr[10]); //数组中是int*
3. 传递多维数组 ??关于第3,4 个定义方式,还是有点问题,
//这2种定义方式的一样的,数组的第一层(),都被转换成 int*, 后面的维度信息都被保存下来了;
P195
void print(int arr[][3]);
void print( int arr[4][3]);
//下面这个方式还是要在想想,因为这里面传递形参等同int (&arr)[3]
//只是arr[2][3] 这类参数可以把里面的地址给当成int, 然后取值也能得到正确数值
void print(int (*arr)[3]);
void print(int *arr[3]); //这个函数传递 arr[2][3] 函数时候会报错,因为无法转换成int** 格式
4. 含有可变形参的函数:
a. 通过initializer_list<T> 传递同种类型的元素, 其中元素是const;
b. 使用 varargs
5. return 返回一个值的方式和初始化一个变量或形参的方式是完全一样的。
返回的值用于初始化调用点的一个临时变量
返回局部对象的引用,局部对象的指针 都是错误的
6. 调用运算符的优先级与 . , -> 相同,也符合左结合律, 可以使用返回结果访问结果对象成员
auto sz = shorterString(s1, s2).size();
调用返回引用的函数的得到的左值,其他返回类型是右值;
main 函数结尾处没有return,编译器会隐式插入一个return 0;
7. 返回数组指针的函数:
EXAM: int (*func(parameter_lst))[dimension]
尾置返回类型(trailing return type)
EXAM : auto func(int i) ->int(*)[10]
decltype : 如果知道返回指针指向那个数组,可以使用decltype 声明返回类型
EXAM : int odd[] = {1, 3, 5, 7, 9};
decltype(odd) *arrPtr(int i); //decltype 不会把数组转换成指针
8. 函数重载:★ 不允许只有返回类型外其他所有要素相同;
a. 只有顶层const 不同,无法区分开
b. 指针 与 引用,通过区分其指向的是常量对象还是非常量可以实现重载
这里本质是底层的const
//传递非常量对象或指向非常量对象的指针时候,编译器优先使用非常量版本函数
int getVal(type&);
int getVal(const type& );
int getVal(int *type);
int getVal(const int *type);
EXAM : {
int getVal(const int (&arr)[4]){
cout << "var type is const int&[4]" << endl;
return arr[0];
}
int getVal(int (&arr)[4]) {
cout << "var type is int& [4]" << endl;
return arr[0];
}
int arr[] = {10, 11, 12, 13};
const int carr[] = {1, 2, 3, 4};
getVal(arr);
getVal(carr);
}
c. const_cast 和重载:
EXAM: { //(CB) :这里有可能是很多封装类里面的操作方式,可以减少更改次数
const string &shorterString(const string &s1, const string &s2) {
return s1.size() <= s2.size() ? s1 : s2;
}
string &shortedString(string &s1, string &s2) {
//这里auto 根据函数返回值推断,这里是包含底层const,所以返回值里面仍然要 const_cast<string&>() 再次转换
auto &r = shortedString(const_cast<const string&>(s1), //必须要const_cast<const string&> 否则就是调用自己,() 都必须要有;
const_cast<const string&> (s2));
return const_cast<string&>(r);
}
}
d. 内层作用域声明名字,将会屏蔽外层作用域的声明的同名实体
不同作用域无法重载函数名
9. 默认实参声明:
//同一个形参只能被赋予一次默认实参,
//一旦某个形参被赋予了默认值,后面的所有形参都必须有默认值
//使用时候,必须从头向后增加参数,不能跳过前面参数
//局部变量不能作为默认实参
10. 函数匹配中,实参类型转换优先级:
1. 精确匹配(实参与形参类型完全相同, 实参从数组或函数类型转换对应指针,顶层const删减)
2. const 转换实现匹配;(这里涉及在引用 与指针中 增加底层const,反之不行!)
3. 类型提升实现匹配;(char ==> int, 这里是类型提升)
4. 算术类型转换(3.12L + 'a' : 'a' :首先类型提升int, 然后和long double 相加时候,在转换成long double)
或者 指针转换(整数0, 字面值nullptr 能转换成任意指针类型, 指向任意非常量指针都能转换成void*( 常量就是const void *))
5. 类类型转换实现匹配
////Chaptor7//////////////
1. 成员函数中,const 关键字放在成函数的参数列表之后,
表示this 是一个指向常量的指针 const TYPE* const = this
只有这样,改类型的常量对象才能访问该操作, 常量成员函数
否则的话,正常成员函数中this = TYPE* const
double Sales_data::avg_price() const {} //类外部定义成员函数,必须保留const
2. 类中定义的某种类型在类中的别名,与其他成员一样也存在访问限制
//而且必须在使用前定义
3. mutable : 可变数据成员,永远不会是const, 即使在常量成员函数中也是可以修改的
class Screen {
public:
typedef string::size_t pos; //必须在使用前定义,外部需要使用时候,就必须是public
inline void some_member() const{ //inline 在声明与定义中都可以有,只在外部定义也是可以
++access_ctr;
}
friend void updatePos(int x); //有些编译器需要提前声明友元函数,有些编译器不需要
//这样的话最好把友元函数声明放在同一个文件中,类声明前面
private:
mutable pos access_ctr; //mutable 可以保证在所有成员函数中修改(即使是const)
};
4. 基于const 的重载: 要考虑到const 变量与非const 变量调用同一个函数时候,传进去的默认的this 指针含义的不同
const type : this = const type * const
type : this = type * const
对于一个返回实例引用的函数,如果使用了const定义为常数成员函数,那么返回的也是const &;
此时再想调用其他的非常量成员函数就会报错
class Screen {
public:
Screen &move(int );
Screen &set(int , char);
const Screen &display();
};
myScreen.display().move(10).set(11, '#'); // 会报错,常量对象无法使用非常量成员函数
//EXAM 解决方案:
class Screen{
public:
Screen &display(std::ostream &os) {
do_display(os); return *this; //这里调用do_display 时候,指针隐式的被转换成const ostream *const ;
//但是返回的时候,还是传进来的ostream * const
}
const Screen &display(std::ostream &os) {
do_display(os); return *this;
}
private:
void do_display(std::ostream &os) const {os << contents;}
};
//好处:避免多处使用相同代码,方便增加新功能或者调试信息; inline 函数,不会增加开销
5. 类声明之后,定义之前是一个不完全类型,只能有限情景下使用
定义指针或引用 //一些链表应用
声明以不完全类型作为参数或返回类型的函数
6. 作用域 与定义在类外的成员
定义在类外的成员函数 必须要使用作用域运算符, 之后的部分就是在类的作用域范围内,可以直接使用类其他成员了
//函数返回类型通常在函数名之前,如果返回类型是 该类类型成员,那也必须加作用域运算符
EXAM : Windown_mgr::ScreenIndex Window_mgr::addScreen(const Screen &s) {}
7. 构造函数:
a. 初始化列表: 成员是const/引用/成员属于某种类型,且该类没有默认构造函数时, 必须将其初始化,
//const 变量是在初始化结束后赋予的,所以无法再次赋值
//初始化列表之后的{},执行的是赋值操作
b. 初始化顺序按照成员变量定义顺序
c. 委托构造函数(delegating constructor)
d. Sales_data obj(); //这里是声明一个函数,而不是定义一个对象
8. 隐式类类型转换:
只能在类内声明构造函数使用,类外定义时不需要
只允许一步类类型转换:
关键字explicit 只对一个实参的构造函数有效,
需要多个实参的构造函数(给一个函数传递多个参数只会对函数参数有限制)不能用于执行隐式转换,所以无需explicit
9. 聚合类: 所有成员都是public, 没有构造函数,没有类内初始值,没有基类,没有virtual 函数。
使用初始化列表初始成员变量,必须按照定义顺序
字面值常量类:
10. 静态成员: 与类本身直接相关,与对象没有直接关联
//只需要在类内声明语句使用static, 类外定义时候不能重复使用static
//本身不包含this 指针(CB: 所以这里对于const 变量调用静态变量成员函数就不需要重载,因为这里面不包含this 指针)
//访问静态成员,可以使用作用域运算符, 对象,引用,指针 都可以访问静态成员
//可以作为内部成员函数的默认值
//可以定义不完全类型对象(普通类型只能定义指针和引用);
//静态成员变量函数不可以使用其他非静态成员变量;
+++++++++++额外知识点++++++++++++++
1. 编译器必须先初始化被包含的成员对象之后再执行构造函数体; 好处是降低随后再构造函数部分调用赋值操作符的开销,在某些情况下,可以避免临时对象的产生;
2. 内联就是用方法的代码 替换 对方法的调用;通过消除调用开销来提升性能,但是滥用的话,会导致代码量变大,有可能出现更多的缓存失败 和 页面错误;
3. 虚函数慢,就是无法内联函数调用,调用就是运行时动态绑定; 模板会好点,毕竟是编译期就把类型解析了;
====Chaptor8============
1. istream : 输入类型,提供输入操作
ostream : 输出类型,提供输出操作
cin: istream 对象,从标注输入读取数据
cout: ostream 对象, 向标准输出写入数据
cerr: ostream 对象,想标准输出写入数据
>> 运算符 : 从一个istream 对象读取输入数据
<< 运算符 : 向一个ostream 对象写入输出数据
getline : 从给定的istream 中读取一行数据,存入一个string 类
cout: 指定输出格式 #include <iomanip>
cout << setprecision(16) << (double) num << endl;
8.1 习题里有几个不错code技巧
while(in >> str, !in.eof()) { // 这里利用,会自动舍弃前面的结果,只拿最后的结果
if(in.bad()) {
throw runtime_error("is stream issue\n"); //利用异常
}
if(in.fail()) {
cerr << "input format issue, pls re-try" << endl; //利用错误输出
in.clear();
in.ignore(10, "\n");
continue; }
cout << str << endl;
}
2. 刷新输出缓冲区:endl & fluse & ends; unitbuf
cout << unitbuf; //所有输出操作立即刷新缓冲区
cout << nonunitbuf; //回到正常的缓冲方式
3.tie交互式系统通常关联输入流和输出流
cin.tie(&cout); //仅仅是展示,标准库已经将cin & cout 关联在一起
//在cin 操作之前,就会把cout 缓冲区刷新(也就是会把cout 缓冲区输出)
4. ifstream/ofstream: getline & in >> str;
//文件使用结束时候要close();
//一个文件流已经打开的话,再次使用open,会使failbit 被置位,后续操作都会失败
istringstream/ostringstream :
========Chaptor9========
1. iterator : auto 决定迭代器类型时候,如果对象是const,begin() 返回的也是const iterator
const vector<int> vi1;
auto iter1 = vi1.begin(); //const iterator
2. vector<const char*> vc{"t1", "ts"}; //这里必须要用const char *, char* 会报错
EXAM:看下面例子就明白了,这里取得就是字符串字面值常量的地址,所以里面的字符串不能修改
const char *getPtr(){
const char *p = "here is the test"; //这里是直接取字符串字面值常量的地址
printf("p addr is %p\n", p);
return p;
}
char *getPtr2() { //无法通过编译
char s[] = "test"; //这里初始化了一个局部变量
return s;
}
3. 只有顺序容器的构造函数接受大小参数,关联容器不支持;
4. 内置数组类型无法赋值 或 拷贝, 但是array容器并无这些限制
int arr[] = {1,2,3,4,5}; //P102
int a[5] = arr; //内置数组无法拷贝
5. assign : 仅顺序容器 //将右边运算对象中所有元素拷贝到左边运算对象,只要求类型相容即可
//赋值会导致左边容器内部迭代器和引用,指针失效; assign 也是一样
swap((vector<int>)a1, a2); //C11Primer 建议使用非成员版本swap
//因为只交换了两个容器内部数据结构,没有拷贝,删除,插入操作, 但是swap 以后这些元素属于不同容器的
//iter开始指向svec1[3],swap 以后指向svec2[3];
6. 关系运算符: == ,!=(所有容器都支持); <, <=, >, >=(除了无序关联容器都支持)
左右两边对象必须是相同类型容器,且必须保存相同类型元素(元素不支持某个关系算符,那这个对象就不能使用对应的运算符)
比较实际是元素逐对比较
看第一个不相等元素比较结果;元素都相同,size 小的小;
7. 顺序容器:insert/emplace_fornt/push_front //插入元素会导致迭代器,引用,指针失效
emplace: 成员函数是将参数传递给元素类型的构造函数(对于内建类型似乎没差别,但是对于一些自定义类型就有意义)
vector<sale_data> vs;
vs.push_back("092-1233", 23, 12.33); //错误
vs.emplace_back("092-1233", 23, 12.33); //正确,
8. 成员访问: iterator:begin/end; 引用: front/back; 下标操作(安全性,不保证不越界): [] ; at(越界会out_of_range 异常):
插入:push_back/push_front; insert; //插入范围元素,返回插入的第一个元素迭代器,单个元素,则是插入元素的迭代器;(CB: 这里注意迭代器/指针/引用失效后,依靠返回值再次生效的做法)
删除:pop_back/pop_front(返回void) ; erase(返回删除后的一个元素迭代器); clear(返回 void)
9. forward_list: 增/删/改 影响的是被删元素前面的一个元素,所以都是提供操作之前的一个元素,
//使用insert_after, emplace_after, erase_after ,
//before_begin 提供了一个前置节点返回,不支持解引用
//不能在尾后迭代器end 之后插入
10. !!!容器中增删改导致 迭代器,指针,引用失效
增加元素:
vector/string : 新插入元素有可能会导致存储空间重新分配,(未重新分配存储空间,插入前的都还有效,后面的就都失效了)
deque : 首尾插入的话,迭代器会失效,但是指针,引用还是有效;
其他位置插入,会导致都失效;
list/forward_list : 插入前后,都有效
删除元素: 被删除的元素的迭代器,地址,引用都会失效
vector/string : 被删除元素的前面元素的,都有效
deque:删除首元素: 都不影响; 删除尾元素:尾后迭代器受影响,其他都不影响; 其他位置元素: 所有的都失效
list/forward_list : 其他位置的都不影响
11. resize/capacity/reserver/shrink_to_fit : 控制容器的内存分配:(shrink_to_fit : 标准库不保证一定会退还内存)
//reserver : 可以开始前指明最少要多少个元素数量,但实际分配大小由标准库决定; 小于当前capacity 时候,什么都不做;
//只有size = capacity 时候,还要增加元素时候,才会触发内存再分配
12. 容器适配器(adaptor): CB: forward_list:不是适配器,其中iterator 是forward_iterator
stack/queue : 基于deque 实现的
priority_que : 基于vector 实现的
stack : back, pop_back, push_back, 所以可以使用除了array, forward_list 其他容器
queue : front, back, push_back, push_front, 可以使用list, deque
priority_que : front, push_back, pop_back, 随机访问,可以使用 vector, deque
////////////////Chaptor10//////////////////
1. 泛型算法: 主要在algorithm , 还有一个在numeric(accumulate)
2. 读操作: equal/accumulate/find/count
写操作: fill/back_insert/copy
//算法不检查写操作,用户需要保证元素有效,例如保证被copy 或者赋值的容器中有足够大的空间
3. lambda : 可调用对象(函数,函数指针, 函数调用运算符重载,lambda)
参数列表/返回类型 都可以忽略;
无法包含默认参数,所以lambda 调用实参数目永远与形参相等
如果函数体只有一个return 语句,则返回类型从返回表达式类型推断出来,否则就是void!
捕获列表只用于局部非static 变量,lambda 可以直接使用局部static 变量,和在它所在函数之外声明的名字
定义lambda 时,编译器就生成一个与lambda 对应的类类型(未命名),这个类里面包含了lambda 所捕获的变量数据成员,
= 按值传递时候:这些变量都是const,想要修改的话,需要在加mutable
& 引用传递时候:能否修改依赖此引用是指向的是const 还是非const 类型
传递参数时候,就是生成一个对象
可以从函数中返回lambda,但是此时lambda 中不能包含引用捕获
[]: 空捕获列表,不能使用所在函数变量
隐式捕获:=, &, 编译器推断捕获序列
混合使用隐式捕获 和 显式捕获 :捕获列表中第一个元素必须是& 或= ,后面显式捕获必须不同
4. bind <functional> : 通用函数适配器 auto newCallable = bind(callable, arg_list);
名字_n 都定义在一个名为placeholders 命名空间中,该命名空间在std 命名空间
bind 那些不是占位符的参数,被 拷贝 !!到bind 返回的可调用对象中, 但是对于想引用方式传递或是要绑定参数的类型无法拷贝(os)
如果希望传递给bind 一个对象而又不拷贝它, 就必须使用标准库reg(cref) 函数;EXAM 有示例
EXAM:
auto check6 = bind(check_size, _1, 6); // _1 为newCallable 第一个参数占位符,_2: 第二个参数占位符
string s = "hello";
check6(s) 等价 check_size(s, 6);
auto g = bind(f, a, b, _2, c, _1); //新调用对象将自己参数作为第三个和第五个传递给f
#include <functional>
auto fr = bind(print, ref(os), _1, ' ');
5. 迭代器:
a). 插入迭代器: back_inserter(总是在最后插入)/ front_inserter(总是插入到最前) / inserter(可以在指定的位置之前插入)
CB: 这里的迭代器能够解决在类中使用插入删除操作导致迭代器有可能失效的问题,这三个迭代器基本包含了container 常规的插入方法了,注意保证container 支持这种方法;
b). iostream 迭代器:istream_iterator<int> int_it(cin) (从cin 读取int)/ int_eof(空就, 可以被认为是尾后迭代器,一旦关联的流遇到文件尾或者IO错误,就与尾后迭代器相等);
允许懒惰求值,当istream_iterator 绑定到一个流,标准库并不保障迭代器立即从流中读取数据,具体实现可以推迟,知道我们使用迭代器时,才真正读取
主要担心是,没有使用就销毁了,或者我们正在从两个不同对象同步读取同一个流
EXAM1:
ifstream in("afile");
istream_iterator<string> str_int(in);
EXAM2:
ostream_iterator<int> out_iter(cout, " ");
for(auto e :vec) *out_iter++ = e; ?(本地编译器后面省略操作试验失败)//这里解引用* & 递增++ 对于流迭代器没有任何作用(out_iter = e),但是这样些比较容易理解含义,以及兼容其他迭代器
cout <<endl;
可以为任何定义了输入运算符(>>) 的类型创建istream_iterator 对象
同理,只要类型有<< 运算符,就可以为其定义 ostream_iterator 对象
EXAM:
istream_iterator<int> int_iter(cin), eof;
ostream_iterator<int> out_iter(cout, "^^\n");
vector<int> vi;
while(int_iter != eof) {
vi.push_back(*int_iter++);
}
sort(vi.begin(), vi.end());
for(auto &x :vi) *out_iter++ = x;
cout << endl;
//这个做法也很有意思!
unique_copy(vi.begin(), vi.end(), out_iter);
c). 反向迭代器 : reverse_iterator
因为很多函数都是利用iterator 中间层实现通用性,这样也可以通过传递riterator, 实现反向查找,排列
注意点:当得到某个反向迭代器参数的函数返回值,通常也是反向迭代器,这时候要正向输出时候,
不能用这riterator,否则返回值是反的,需要使用 .base() 方法得到一个正向迭代器
因为iterator 都是左闭右开规则,所以此时 rcomma.base() 会变成后面一个iterator
类似于crbegin() & cend() 关系,也是整体向右移动一位,实现左开右闭
EXAM:
string s1 = "test, here, there, check";
auto rcomma = find(s1.rbegin(), s1.rend(), ',');
cout << string(s1.rbegin(), rcomma) << endl;
cout << string(rcomma.base(), s1.end()) << endl;
d). 移动迭代器: 13 章会介绍
6. 泛型算法结构:
迭代器类型:(等级逐渐提升,并兼容之前的迭代器的操作)
输入迭代器(input iterator): 只读,不写;单遍扫描,只能递增
输出迭代器(output iterator): 只写,不读;单遍扫描,只能递增
前向迭代器(forward iterator): 可读写, 多遍扫描,只能递增
双向迭代器(bidirectional iterator): 可读写,多遍扫描,可递增递减
随机访问迭代器(random-access iterator): 可读写,多遍扫描,支持全部迭代器运算,独有的(<, <=, >, >=, +, +=, -, -=,下标运算符iter[n] 等价于 *(iter[n]) );
7. list & forward_list 对一些通用算法定义了自己特有的版本,与通用版本不同,这些链表特有的版本会修改给定的链表
////////////////////Chaptor11 关联容器////////////////////
1. 共有8 个关联容器,按照 重复,排序,set/map 分类
无序容器使用哈希函数来组织元素
2. map/set : key采用的类型必须要是右强定义< 的类型,否则需要传递一个比较函数
EXAM:
multiset<Sales_data, decltype(compareIsbh)*> bookstore(compareIsbn); // 这里使用decltype 获得函数指针类型,必须加上*,来指出我们要使用一个给定函数类型的指针
3. pair <utility>
//只有有限的几种操作: make_pair(), p.first, p.second, p1 (> / < / == / !=) p2
4. key_type/ value_type /mapped_type(只有map/unordered_map/multimap/unordered_multimap 有这个类型):
map<string,int>::value_type : 是pair<const string, int>
map<string,int>::key_type : string
map<string,int>::mapped_type : int
map的value_type 是一个pair, 可以改变pair的值,但是不能改变关键字成员的值(pair 中type 定义的也是const type)
set 中iterator & const_iterator 两种类型允许只读访问元素,但是不能改变,因为key_type 是const
map 中iterator 是可以修改 mapped_type 对应的数据的
通常不对关联容器使用泛型算法,key_type 是const 这一特性意味不能将关联容器传递给修改或重排容器元素的算法
只能由于只读取元素的算法,但是一般专用的(例子find)成员函数会更快;
5. []操作符: set类型不支持下标操作
与vector 的下标操作不同点: 如果使用一个不在容器内的关键字,会添加一个该关键字元素
at: 访问关键字,如果不在的话会抛出一个异常
上面两个不适用 multimap, 因为有多个关键字对应
map 中insert 与 [] 对于重复插入元素操作不同, insert 对于后面重复关键字会返回<对应的iterator,false>,不改变原来value
[] 则会不断覆盖原来的值
6. upper_bound(k) & lower_bound(k) 不适用与无序容器(unordered_...), 因为这个返回的是第一个关键字大于(不小)于k的元素
//可以用这个来查找multimap 中某个关键字范围,没有这个关键字,会返回给定关键字的插入点位置,两者仍然相等; 与下面equal_range 功能类似
equal_range(k) : 返回一个迭代器pair, [first , end), 如k不存在,则两个都为c.end();
7 对于multiset/multimap 中查找元素,其中关键字重复元素在容器中相邻存储
EAXM1:
auto cnt = s.count(search_item);
auto iter = s.find(search_item);
while(cnt) {
cout << iter->second << endl;
++iter, --cnt;
}
[lower_bound(search_item) ~ upper_bound(search_item) )
equal_range(search_item);
8 无序容器 与 对应的有序容器 本质是不同的东西,定义的函数模板也不一样
unordered_map/set & unordered_multimap/set (key_type,mapped_type, hash<key>, equal_to<key>, allocator ) , 底层实现是hash map
map/set & multimap/set : 模板参数类型(key_type, mapped_type, less<key> ,allocator), 底层实现是 RB tree
////////////////////////////////////////其他地方想起来的知识点////////////////////////////////////////
1. 定义一个在主函数开始前的操作
//exam:
__attribute__((constructor))
void test() {
cout<< "do some constructor before main func\n";
}
2. strace ./a.out //可以追踪函数调用过程,包含用户态
3. 对齐:
bit : 位(一个二进制位)
byte : 字节(8 bits)
word : 字(电脑一次处理事务的固定长度, 一般N位系统的字长为 N/8);
内存对齐: a. 各个成员变量起始地址相对于结构起始地址必须是正数倍;
b. 结构大小为该结构体中占用最大空间类型的整数倍;
EXAM :
struct Node {int a; double b; char c}; size = 24, 因为b 变量的起始地址要为8, 所以b 从8 开始(4~7 填充dummy), 最终的结构体size = 24(要保证最终大小是8 的整数倍)
struct Node {int a; char c; double b}; size = 16, 这里b 正好从8 开始,然后总size = 16, 也是8 倍数;
4 .软件性能 与 灵活性 之间存在一种基本平衡,太灵活的设计一般性能都不会太高;所以满足需要即可,不用过度设计;