Primer C++ 学习笔记
第一章
输入输出 IO语句。iostream 中的 istream 和 ostream
输入运算符及输出运算符
while语句
for语句
读取数量不定的输入数据 while(cin >> value) 用类似cin的istream类返回语句作为判断
if else 语句 else是最近匹配原则
类实现
第二章 变量和基本类型
基本算术类型转换
赋予无符号类型超范围数值,结果数值是对数值总数取模后的余数
赋给一个带符号类型超范围数值,结果是未定义的
字面值常量
以0开头的整数代表八进制数
以0x或0X开头的整数代表十六进制
字符串字面值常量实际上是由常量字符构成的数值
注意有一类字符为不可打印故需要用到反斜线,正则表达式中 应进行两次转义//
初始化
列表初始化:有大括号括起来的一系列初始值
默认初始化
没有指定初值
复合类型
[&] 引用:所有操作都是在与之绑定的对象上进行的
[*] 指针:赋予对象地址值或nullptr空值
const限定符号
const:对象一经创建,值无法改变。 可以用字面值或左值来初始化
const 变量可以用非const值初始化同时可以用来初始化非const变量
const的引用 指针 const int &v const int *v 称为顶层const int *const v 底层const 指针所值对象不能发生变化
constexpr 变量:编译时就可以计算得出的变量
处理类型
auto 可推断数据类型 一般会忽略掉顶层const,需要明确指出 const auto
如果 decltype 使用的是个不加括号的变量,则得到的结果就是该变量的类型如果给变址加上了或多层括号,编译器就会把它当成是个表达式,变量是可以作为赋值语句左值的特殊表达式,所以这样的 decltype 就会得到引用类型
第三章 字符串、向量和数组
String Vector及迭代器相关内容见泛型编程
数组
定义类型 形如a[d] 维度d必须为一个常量表达式,即在编译时可以求其值
可以进行列表初始化,允许忽略数组的维度,数量计算并推出维度可从列表初始值的
复杂的数组声明 int *ptrs[10] ptrs是含有10个整型指针的数组 int (*ptrs)[10] ptrs指向一个含有十个证书的数组
数组名可转换为数组首元素的指针
C风格字符串 strlen§ 返回长度 strcmp(p1,p2) == 0, > + , < - strcat(p1,p2)等同于string中p1+=p2 strcpy(p1,p2) p2拷贝给p1 string::c_str() string到c风格字符串的接口
being(数组名),end(数组名)返回数组首元素的地址与尾元素后一个地址,左闭右开
多维数组
int ia[3][4] ia可转换为含四个整数的数组的指针
int (*ip)[4] 前面括号是对类型的声明 后面中括号表明指针指向数组类型 int到括号部分为数组元素的定义
第四章 表达式
左值与右值
一个对象用的是右值的时候,用的是对象的值(内容),一个对象是左值的时候,用的是对象的身份(在内存中的位置)
当一个左值被当做右值使用时,实际使用的是它的值
逻辑关系运算符
逻辑与和逻辑非: 逻辑与只有在左侧运算对象为真时才会对右侧运算对象求值 逻辑非只有在左侧运算对象为假时才会对右侧运算对象求值
递增递减运算符
建议:除非必须,否则不用递增递减运算符的后置版本 后置版本储存开销比前置版本大
条件运算符
cond ? exprl : expr2;
位运算符
逻辑简单,但若用于编码,很强大(使用一类表达特征的算法)
类型转换
隐式转换: 数组自动转换成指向数组首元素的指针 int ia[10] to int *ip = ia;
显式转换:static_cast 任何具有明确定义的类型转换 只要不包含底层const 都可以使用 static_cast 一个重要的应用:用static_cast找回存于 void*指针的值:
显式转换:const_cast 消除底层const,不会改变顶层const属性,同时也不能改变表达式的类型
reinterpret_cast reinterpret_cast运算符是用来处理无关类型之间的转换;它会产生一个新的值,这个值会有与原始参数(expressoin)有完全相同的比特位。
第五章 语句
Default是一种跳转指令,跳转到对应的case并逐个运行到达语句边界
for语句的init-statement可以定义多个对象,但是只能有一条声明语句: 即所有变量的基础类型必须相同
goto语句使用: 跳转到某带标签语句 带标签语句定义如 end: return
异常处理
throw与语句抛出一个异常类
在由内到外的域内找一个catch语句与之匹配进行异常处理操作,如未找到调用terminate结束程序
常见异常类见第十九章异常类
第六章 函数
函数基础
形参实参 实参是形参的初始值,一个实参初始一个形参
局部静态对象 在程序第一次经过对象定义语句时初始化,并且程序终止才被销毁 形容 static size_t ctr
建议变量或者函数在头文件声明,在源文件中定义
分离式编译
参数传递
初始值拷贝给实参,拷贝引用,指针都可对原实参改变
可以用非常量去初始化一个底层const对象,但是反过来不行
底层const const& p const* p const作用于所指对象本身
顶层const const int,int *const p 作用于声明的对象本身
尽量使用const引用,空间资源占有少,安全
数组引用 int (&arr)[10] 后中括号规定引用数组的长度限定
数组多维指针(*matrix)[10]含有十个整数数组的指针 解指针得到一个一维数组,指针+1得到下一个一维数组
main传参 in main (int argc , char *argv []) 第一个参数表示传入参数的数量 第二个形参是一个数组,元素是指向C风格字符串的指针(字符串数组) argv[0]保存程序的名字,可选的参数从argv[1]开始
返回类型及return语句
数组不能被拷贝,所以函数不能返回数组,不过可以返回形如 (*p)[10]的指针。 返回指针的函数声明 int *func(int i) [10] 或者使用尾置类型auto f(int i) -> int(*)[10]
函数重载
函数名字相同,当形参列表不同
形如const int ,int 是同一种参数列表,不同被重载
调试帮助
asset(expr) 表达式为假则assert 输出信息并终止程序的运行
NDEBUG,使assert失效
函数匹配
1.精确匹配 类型相同 数组或函数转化为指针 添加顶层const
2.底层const转换,一般是非const转换为const形参
3.类型提升 int->long long float->double
-
算术类型转换或指针转换
-
类类型转换
第七章 类
抽象数据类型
this 指针 编译器负责把引用类的地址传递给isbn的隐式参数this
友元
类想把 个函数作为它的友元,只需要增加一条以friend 关键字开始的函数声明语句
字面值常量类
类成员为字面值常量或者是constexpr成员函数
类的静态成员
通过在函数成员声明之前加上static使之与类关联在一起
存在与任何对象之外,只与类有关
使用作用域运算符直接范文静态成员 Salesdata::isbn
可再类内直接定义,也可在类内声明在类外通过作用域运算符进行定义 定义时不加static
第八章 IO库
IO类
不能对IO对象进行拷贝或者赋值,函数形参均为传递引用
读取失败 s.fail置位,读取到文件为 s.eof()置位 s.clear()进行复位
缓冲区刷新: 缓冲区满时 程序正常结束时 unitbuf可以设置输出操作后立即刷新 操作符 读写被关联的流
操作符 endl 添加换行 ‘\n’ 后刷新 ends 添加一个空字符 ‘\0’ 刷新 flush 不添加字符立即刷新
文件输入输出
fstream ifstream ofstream
文件模式 初始化时作为第二个变量来控制 in 以读方式打开 out 以写方式打开 app 每次写操作定义到文件末尾 ate 打开文件立即定位到文件末尾 trunc 截断文件
string 流
sstream istringstream ostringstream
第九章 顺序容器
push_back(), push_front()是进行拷贝 emplace是构造元素
forward_list 插入或删除元素
容器适配器 stack queue priority_queue
第十章 泛型函数
定义在 algorithm库 ,numeric库
常用泛型
排序 sort
equal(r1.begin(),r1.end(),r1.begin())可以检查不同容器
fill(v.begin(),v.end(),0)
拷贝算法 copy(r1.begin(),r1.end(),ins.begin())ins是一个插入迭代器以保证有足够空间
Unique(begin(),end())返回值指向不重复值末尾的迭代器,搭配erase函数erase(Unique(begin(),end()),end())
排序算法 sort(r.begin(),r.end(),cmp) cmp为谓词(函数指针),可指定函数或者lamda表达式[铺货至](const string&a, const string &b){return a.size()<b.size()} 单行函数可推断出返回类型,多行需指定 ->
{=html}bool
for_each 对迭代器范围内满足条件的变量进行
参数绑定
auto check6 = bind(check_size , _1, 6) ; 而参数函数转换为一参数函数,_1,6占位符表示将6填入check_size函数第一个参数内
需声明命名空间 using std : : placeholders : : _ 1
迭代器
插入迭代器: it =t 在it的当前位置插入t,插入后迭代器会移向下一个可插入位置 back_inserter->push_back() front_inserter->push_front() inserter() 接受第二个迭代器参数,元素插入到这个迭代器所表示的元素之前
流迭代器 istream_iterator<t>
{=html} in (is) in从输入流is读取类型为T的值 in++读取下一个值 istream_iterator<t>
{=html} var var为任意表示输入流尾的流迭代器 ostream_iterator<t>
{=html} out(os); ostream_iterator<t>
{=html} out(os,d); 类似与普通迭代器,不过会导致输入输出
反向迭代器 :rbegin(),rend()操作反向
特定容器算法
链表类型List 和 forward_list定义了自己的容器算法
第十一章 关联容器
关联容器类型
有序容器
有序容器的底层排序由红黑树实现
对于非内置类型的参数,必须定义一个类型之间的弱序关系
方法1. 在类内部定义 operator <的运算符重载(一般声明为友元以方便使用)>
{=html}
方法2. 定义一个比较函数 声明 map或set时模板类进行制定 multiset<sales_data, decltype(compareisbn)*>
{=html} bookstore(compareIsbn) 其中第二个参数为可调用对象的类型。比较函数的类型为函数指针。
无序容器
无序容器使用一个哈希函数和关键字类型==运算符,在满足按值查询且不要求容器排序的情况下性能比有序容器好很多
定义了一组无序容器的管理操作
第十二章 动态内层
智能指针
为了更容易(同时也更安全)地使用动态内存,标准库提供了两种智能指针(smart point)类型来管理动态对象 。智能指针的行为类似似常规指针, 重 区别是它负责自动释放所指向的对象
shared_ptr<t>
{=html} shared_ptr允许多个指针指向同一个对象 同时指向同一对象的指针共同维护一个变量表示指向此对象的shared_ptr个数 当某shared_ptr指向对象改变或被销毁时,原指定对象的引用数量若为0,则会销毁原对象的动态内存。可用make_ptr<t>
{=html} 初始化一个动态内存并返回 shared_ptr可以与new混合使用但是不能用=赋值给share_ptr(无法完成内置指针到智能指针的动态转换),必须是形如 shared_ptr(new int§) 显示的将一段动态内存绑定到智能指针上
Unique_ptr unique_ptr没有make_shared, 需要将其绑定到new返回的指针上 unique_ptr<t, d>
{=html} u{d) T可调用对象类型, (函数指针类型), d可调用对象(函数指针)
Unique_ptr VS auto_ptr auto_ptr 复制和赋值会改变资源的所有权。策略不严格。。可能导致一个指针被删除两次。unique_ptr不允许赋值与拷贝。unique_ptr十分依赖于右值引用和移动语义。
weak_ptr<t>
{=html} 指向由shared_ptr 管理的对象 由千对象可能不存在,我们不能使用 weak_ptr 直接访问对象,而必须调用lock。若对象不存在,及.use_count()=0 .expired()=true .lock()返回一个空shared_ptr。否则返回一个指向对象的shared_ptr
动态数组
new进行分配: 形如 int* p = new int[n] 释放内层 delete [ ] p;
allocator类 .allocate(n)函数会返回可构造的首地址
第十三章 拷贝控制
拷贝
拷贝构造函数 class Foo{ public: Foo(); Foo(const Foo&); // … };
一般传入 拷贝对象的const引用 拷贝一般用内部成员的构造函数实现
赋值
利用重载 = 运算符实现
class Foo{ public: Foo& operator=(const Foo&); // … };
返回一个指向其侧运算对象的引用 返回this指针
销毁 (析构函数)
包括智能指针在内的类型都会默认析构 如内类显示声明的动态内存,而无法默认析构的情况 需另声明析构函数进行显式销毁 一般来说:需要另外定义析构函数的类也需要定义构造拷贝与构造赋值函数
拷贝控制=defalt显示地要求编译器生成合成的版本
拷贝控制 = delete可将拷贝构造或拷贝赋值定义为已删除的函数 旧标准将拷贝控制声明在private:里以组织拷贝
对象移动
为了支持移动操作 新标准引入了 引用类型------右值引用,所谓右值引用就是必须绑定到右值的引用。有一个重要的性质------只能绑定到一个将要销毁的对象
标准库提供了一个一个将左值转换为绑定在左值上的右值引用 std::move()
移动构造函数 Foo::Foo(Foo &&s) noexcept : ele(s.elem) {s.ele = nullptr} 注意传递之后应保证源对象不再指向被移动的资源同时要使其处于可被析构的状态
第十九章 特殊工具与技术
控制内存分配
new操作实际上做了两个工作 1. 使用operator new分配一个未初始化的空间 (返回一个 void* 指针) void*指针可指向任何类型的地址 2. 利用new中传入的变量参数对这一段分配的地址进行初始化
标准库定义了 operator new 和 operator delete的八个重载版本
size_t参数为指定类型实参所需的字节数
运行时类型识别
dynamic_cast指针:用于将基类的指针或引用安全的转换为派生类的指针或引用 使用形式: dynamic_cast<type *>
{=html}(e) dynamic_cast<type&>
{=html}(e) dynamic_cast<type&&>
{=html}(e)
转换发生错误常见情况 指针指向类型不为目标类型的对象或其公有派生类的对象 公有派生类的对象说明此派生类不能对转换类型进行多次继承,(多次继承必须为对转换类型的私有派生类)
type_id 运算符 操作结果的结果是一个常量对象的引用,可用于比较引用或指针所指向对对象是否一致。 当运算对象不属于类类型或者是一个不包含任何虚函数的类时,typeid运算符指示的是运算对象的静态类型,而当对象是定义了至少一个虚函数的类的左值时,typeid的结果直到运行时才会求得
type_info类
枚举类型
限定作用域的枚举类型 enum class open_modes {input, output, append};
不限定作用域的枚举类型 enum color {red,yellow,green}
默认情况下,枚举值从0开始,依次加1。不过也可在声明是为其指定专门的值
类成员指针
声明一个指针专门指向某一类型的成员 (函数指针与普通指针类型,需要指定指针输入参数及返回类型) const string Screen::*pdata pdata = &Screen::contents
嵌套类
注意嵌套类和外层类是相互独立的
Union
一种节省空间的类,类所占空间大小为Union中最大空间成员的空间大小
匿名union中对象可被外层作用域直接访问
对于复杂的union对象,可借用类对Union进行管理
局部类
定义在函数中的类 局部类不能使用函数作用域中的临时变量,类型名,静态变量,枚举成员可访问
不可移植特性
位域:组织类型是机器相关的 用Bit进行声明 Bit mode: 2 //mode占2位
volatile 当对象的值可能在程序的控制或检测之外被改变时,应该将该对象声明为 volatile
链接指示
用于指出任意非C++的语言,以使其调用响应编译器生成代码
声明非C++的函数 extern “C” size_t strlen(const char*); extern “C”{ /*…*/ }
可应用至整个头文件 extern “C” { #include <string.h>
{=html} }
第十八章 用于大型程序的工具
异常处理
抛出异常的栈展开 当throw出现在 try 语句块内时,检查与该 try 块关联的 catch 子句。如果找到了匹配的 catch 就使用该 catch 处理异常。如果这步没有到匹配的 catch 且该try 语句嵌套在其 try 块中 ,则继续检查与外层 try 匹配的 catch 子句。
异常对象
类型 exception 仅仅定义了拷贝构造函数、拷贝赋值运算符 个虚析构函数和一个名为 what 的虚成员。其中 what 函数返回 cons char*, 该指针指向个以 unll结尾的字符数组,并且确保不会抛出任何异常。
命名空间
注意继承的范围,同时命名空间内部的变量会对外层空间的同名变量进行隐藏
命名空间可多次打开关闭
using 声明 如 using std::cout 一次只引入命名空间的一个成员
using 指示 如 using namepace std; 因为引入的变量较多,容易与外层域的变量冲突,故建议使用using声明而不是using指示
多重继承与虚继承
虚继承: 在虚派生中,虚基类是有最低层的派生类初始化的,即派生类应负责虚基类的初始化以防止不同类对虚基类构造函数不同而带来错误
第十七章 标准库特殊设施
tuple 类型
定义tuple explict 不能直接 = {list} 无法完成类型转换赋值,必须显示构造 tuple<string, vector<double>
{=html} , int , list<int>
{=html}> someVal (" constants ", {3 .14 , 2.718) , 42 , (0 , 1 , 2 , 3 , 4 , 5))
访问成员 get<index>
{=html} 访问第index个元素
tuple可用于函数返回多值
make_tuple(var1,var2,…)可用于快速建立tuple,搭配tie使用 tie(f1,f2,…)= make_tuple(var1,var2,…)可用于快速赋值
bitset类型
正则表达式
简单使用 regex类,可以以string字符串或c风格字符串进行初始化
smatch表示string类型的输入序列 cmatch表示c风格字符串(字符数组)的输入序列
一般使用用法 r = regex(string s); //定义一个正则表达式类 cmatch result; //定义一个输入序列 string str; //欲匹配正则表达式的字符串 regex_search(str,results,r); //匹配 cout << results.str(); //打印匹配结果
利用sregex_iterator进行匹配 for (sregex_iterator it(file.begin() , file.end() , r), end_it; it != end_it; ++it) cout << it->str() << endl;
随机数
随机数引擎: default_random_engine e ; // 生成随机无符号表 从伪随机数表头开始生成一系列无符号数
随机引擎操作: e.seed(s)使用s重置引擎的状态
若随机数生成在子函数之中,为保证随机性 1. 将随机数引擎设置为static静态的,以保证每次都会使引擎向前推进 2. 使每次随机数引擎的种子值变化,(以time为种子)
使用分布类型来转换随机数引擎生成的无符号数为给定分布类型的数 均匀分布:uniform_int_distribution<unsinged>
{=html} u(a, b);
IO
格式化输入输出
注意两者差异
流随机访问
第十六章 模板与泛型编程
定义模板
定义函数模板 template<typename t, unsinged n>
{=html} int add(T &p[N]) tyname可以表示为一个推断的类型参数,unsinged可以表示一个常量值
定义类模板 template <typename t>
{=html} class
函数模板在使用时可以直接根据传入参数类型进行隐式转换,类模板必须显式定义
类模板的模板函数友元声明 1.一对一友好关系,友元模板函数的模板参数类型与模板类一致,表示此函数一种参数实例化是同等参数模板类的友元 2.让所有的实例都称为其友元,友元声明中使用于类模板本身不同的模板参数
类模板的static成员 类模板也可以声明static成员,在此情况下,每个不同的实例会维护不同的static成员实例
新标准中,可以为函数和类模板提供默认实参,默认实例化为:template<>
控制实例化 在多个文件中实例化相同模板的额外开销可能非常严重。 extern template declaration //实例化声明 template declaration //实例化1定义 可能有多个extern声明,但必须只有一个定义 由于编译器在使用 个模板时自动对其实例化,因此extern声明必需出现在任何使用此实例化版本的代码
模板实参推断
顶层const无论是在形参中还是实参中都会被忽略
当一个函数参数是模板类型参数的一个普通(左值)引用时(即,形如T&), 绑定规则告诉我们,只能传递给它左值
当一个函数参数是模板类型参数的一个普通(右值)引用时(即,形如T&&), 绑定规则告诉我们,只能传递给它右值
上述规则对move不适用,涉及到引用折叠
可变模板参数
template <typename t, typename ... args>
{=html} … Args表示一个模板参数包 void foo (const T &t , const Args& … rest); rest为一个函数参数包
包扩展 1.定义一个参数的实例化 2.迭代调用一个参数和后续参数包的函数,递归使用参数包内元素
模板特例化
定义方式如: template<> int compare (const char* const &p1, const char* const &p2) { return strcmp (p1, p2); }
第十五章 面对对象程序设计
定义基类与派生类
作为继承关系中根节点的类通常会定义一个虚析构函数 vitual ~foo()
派送吗必须在其内部对所有重新定义的虚函数进行生命 前加vitual关键字或者函数形参列表之后加一个override关键字
运行时绑定,能用同一段代码分别处理 基类和 其派生类 的对象。
每个类控制它自己的成员初始化过程,同时也可以调用其基类初始化其基类元素
可以将类声明为final的以拒绝其他类的继承请求
抽象基类
可以将一个虚函数定义为纯虚函数 及在函数体的位置 =0 以表示此类无实际意义
抽象基类无实际意义,是对其他实例提供一组抽象接口。以方便一类派生函数的定义
访问控制
假设D继承自B
只有当D公有地继承B时,用户代码才能使用派生类向基类的转换如果继承的方式是受保护的或者私有的,则用户代码不能使用该转换
不论D以什么方式继承B,D的成员函数和友元都能使用派生类向基类的转换;派生类向其直接基类的类型转换对与派生类的成员和友元来说水远是可访问的
如果D继承B的方式是公有的或者受保护的,则D的派生类的成员和友元可以使用D向B的类型转换;反之,如果D继承B的方式是私有的,则不能使用。
总的来说,三种继承方式改变的外部类实例对基类元素的访问权限与基于派生类的派生对基类的权限控制。派生类自身成员对基类的访问权限不会受继承方式的影响。派生类的成员或者友元都能访问基类的公有成员和受保护成员
动态实例
动态类型是对类的引用与类指针,知道实际程序运行时才能确定为基类还是各派生类,因此。对于使用此动态类型的接口,在不同类型的条件下会表现出不同的性质。
容器与继承 若要在容器内储存不同类型的元素,不能直接储存。直接储存不能使用动态类型 一般使用指针(最好为指针指针,会自动调用类的析构函数)
对于派生类的构造与析构函数 构造:依次调用基类构造函数,故构造顺序是从最外层元素到内层元素 析构:顺序与构造相反,先析构派生类的成员然后调用外层析构
第十四章 重载运算与类型转换
基本概念
当一个重载的运算符是成员函数时,this绑定到左侧运算对象。成员运算符函数的(显式)参数数量比运算对象的数量少一个。非成员函数相同
普通表达式的等价函数调用 data1 + data2 ; operator+(data1, data2);
基于"调用"的表达式对成员运算符函数的等价调用 data1 += data2; data1.operator+=(data2);
传入对象最好为const 引用 安全同时空间开销小
下标运算符
下标运算符必须是成员函数
形如 const std: :string& operator[] {std :size n) const
函数调用运算符
形如 void operator () (const string &s) const { os << s << sep ; }
如果类定义了调用运算符,则该类的对象称作函数对象,适用于泛型的调用对象
lambda也是一种函数对象,但是其类型无法给出
标准库定义的函数对象
标准库function类型 function<t>
{=html} f f可以匹配一个与T类型相同的可调用对象 function<int(int, int)>
{=html} f1 = add; //匹配函数指针 function<int(int, int)>
{=html} f2 = divide(); //匹配可调用对象 function<int(int, int)>
{=html} f3 = [ ] (int i, int j) { return i * j; } //匹配lambda
类型转换
operator type( ) const 例: operator int( ) const { return val; }
声明为隐式转换易发生错误,可用explicit定义为显示转换 operator int( ) const { return val; }
不要令两个类执行相同的类型转换 如果 Foo 类有一个接受 Bar 类对象的构造函数,则不要在 Bar 类中再定义转换目标是 Foo 类的类型转换运算符。
不要定义转换到多种算术类型的类型转换。让标准类型转换完成向其他算术类型转换的工作。即:对一类可互相转换的类型,只需要声明其中一个类型转换函数即可