读书笔记 - C++ Primer 第五版

几次面试下来,我感觉,基础不造好,如何搭建高楼,尤其看了effective c++后,我更加觉得基础的重要性。

之前自学C++用的一本什么清华大学的,感觉不够专业,这次学习之余,重点是C++ 11的一些特性。

C++11对于C++98来说,试一次本质的飞跃。

第一章 开始

标准库,4个IO对象,cin,cout,cerr,clog,不用using namespace std;每个cout都得std::,endl也得是std::

读取数量不定的数据,就得while(cin>>xx),直到遇到文件结束符,ctrl+z在windows也是可以的

编译器常见的3种错误:1语法错误(少分号);2类型错误(int复制字符串);3声明错误(用到了未定义的)


第二章变量和基本类型

float和double分别有716位有效位;

执行浮点数运算选用double,这是因为float通常精度不够而且双精度浮点数和单精度浮点数的计算代价相差无几。

初始化不是赋值,初始化的含义是创建变量时赋予其一个初始值,而赋值的含义是把对象的当前值擦除,而以一个新值来替代。

默认初始化,此时变量被赋予了“默认值”。定义任何函数体之外的变量被初始化0(全局了?),定义在函数体内置类型变量不被初始化。

声明使得名字为程序所知,一个文件如果想使用别处定义的名字,则必须包含对这个名字的声明。而定义负责创建与名字关联的实体。

extern int i;//声明i不是定义i                          int j;//声明并定义了j

在函数内部,如果试图初始化一个由extern关键字标记的变量,会出错(意思是,有个变量在其他cpp已经定义了,你要用extern声明,就不能初始化(因为只能被定义一次))

复合类型:指向基于其他类型定义的类型。引用指针是,感觉引用的本质是const* xxx p;(在底层,引用变量由指针按照指针常量的方式实现,但还是有区别例如,存在常量指针数组,却没有引用数组)

C++11多了一个nullptr用来作指针的空指针(而且是推荐)

void*指针,可以存放任意对象的地址。

因为const对象一旦创建后其值就不能再改变,所以const对象必须初始化

引用不能指向变量,但是常引用可以const int &r1 = 42;

用名词顶层const表示指针本身是个常量,用底层const表示指针所指的对象是一个常量

constexpr变量,由编译器验证变量的值是否是一个常量表达式 constexpr int mf = 20;

constexpr和const不一样,constexpr int *q =  nullptr;q是个常量指针(直接变顶层)

类型别名typedef 。

C++11多了一种,使用别名声明来定义,using SI = Sales_item;

注意理解typedef 后的真正含义,不能像define简单替换,需要理解整体。

C++11,auto,让编译器自己去分析表达式所属的类型。auto得有初始值,auto一般会忽略掉顶层const(不好理解,感觉是,不写const,他没法自己变成const,得明确表示,const auto &j =42;)

C++11,decltype类型指示符,返回操作数的数据类型,一般用法decltype(f()) sum = x;

decltype((variavle))结果一定是引用,decltype(variavle),只有decltype是引用,才是引用。

C++11规定,可以为struct提供类内初始值

预处理器之所以有一个#ifndef SALES_DATA_H,是看这个是否定义过,没定义过,则下面有效,否则,无效。


第三章,字符串、向量和数组

头文件不应包含using声明,防止冲突

string == string是可以的,大小写敏感

C++11 for(auto &c : str)//遍历str中的每一个字符。

定义初始化vector对象vector<T> v3(n,val) vector<T> v5 = {a,b,c,d}(C++11);

假如对象只需要读,而不需要写,最好采用const_iterator的常量迭代器,保护数据

另外auto it3 = v.cbein();//it3是一个 vector<int>::const_iterator。

记住:一旦使用了迭代器的循环体,就别往容器里面添加元素(因为扩张后,vector可能扩容,换片区域,迭代器就

失效了)数组是不允许拷贝和赋值的。

经典的for处理多维数组:for(auto &row:ia) { for(auto&col :row) { col xxx} }


第四章,表达式

C++11规定,商一律向0取整,直接切除小数部分。

后置++就是一种资源浪费,不是原子操作。后置会取出来,然后+1,再保存。而前置,直接取出+1返回了。

赋值运算=和条件运算?:优先级都很低,记得加括号

C++11允许我们使用作用域运算符来获取类成员的大小

强制转换要避免。

运算符号优先级:->,后置++,cast_name<>(expr),前置++、~,!,()类型转换,sizeof


第五章,语句

C++里,if else匹配和缩进无关,最好加{}来避免歧义

C++11范围for语句for(declaration:expression) { statement };exp:for (auto &r :v)//v是一个vector

C++异常机制,throw 表达式;try 语句块;一套异常类

throw (表达式);抛出异常并终止当前函数,并把控制权转移给能处理该异常的代码。

try的用法和java很相似


第六章,函数

C++11新标准,函数可以返回花括号包围的值的列表

return {“xxxxx”,"sdasdsa"};如此

函数重载,是编译器来确定的,编译器判断,应该用哪个

printf(“ %d %d ”, *ptr, *(++ptr)); //因此这个是先执行++ptr,所以两个的输出是一致的

函数参数入栈的顺序,一般VC的编译器是从右往左入栈,那么这个运算也自然是从右往左。

内联函数避免函数调用开销(然后inline只是一个请求,编译器不一定采纳)

C++11,constexpr函数是指能用于常量表达式的函数,函数的返回类型及所有形参的类型都得是字面值类型

调试帮助(发布得去掉)assert、NDEBUG


第七章 类

C++11,可以使用=default来要求编译器生成构造函数

构造函数,初始值列表,顺序和类定义的顺序一致,构造函数初始值列表中的初始值的前后关系没有影响

使用class和struct唯一的区别就是默认访问权限不一样,struct是public。

假如目的是,类允许其他类或者函数访问他的非共有成员,是令该类或者函数成为他的友元(自认为友元不好,

因为破坏封装性)

mutable(可变数据成员)注意:如果一个结构体或者是类的成员变量被static或者const修饰的话,则不可以再用

mutable来修饰,否则回报编译错误)。一般情况下,如果一个函数、对象等被const 修饰,那么它将无法修改其

成员变量的值,但是如果这个成员变量是被mutable修饰而又不被static或const修饰的话,则该成员变量的值也可

以被修改这个变量的目的是(mutable 是为了突破 const 的限制而设置的,const类,变量是mutable依旧可以变,

同理,struct,关键点!!const了类,但是里面有mutable变量,可以单独改!构造函数使用,初始化表的另一个

明显作用,例如有一个private const int ci; int &ri;你在构造函数里

xxx::xxx(int ii)
{
      i = ii;
      ci = ii;//错误,不能给const赋值
      ri = i;//错误,ri没有被初始化
}
只能,这个是正确的
xxx :: xxx(int ii):i(ii),ci(ii),ri(i) {}
C++11委托构造函数:使用他所属类的其他构造函数来执行自己的初始化过程 xxx():yyy(a,b,c){},委托给yyy

通过加explicit来阻止转换,且explicit的的构造函数只能直接初始化,机 xxx a();不能xxx a = b;

聚合类:假如,所有成员都是public,没有定义任何构造函数,没有任何类内初始值,没有基类,也没有virtual函数

(Struct?)

constexpr函数要求所定义的函数足够简单以使得编译时就可以计算其结果
constexpr还能修饰类的构造函数,即保证传递给该构造函数的所有参数都是constexpr,那么产生的对象的所有成员

都是constexpr, 是一种很强的约束,更好的保证程序的正确定语义不被破坏;
编译器可以对constexper代码进行非常大的优化,例如:将用到的constexpr表达式直接替换成结果;


第八章IO库

IO对象无拷贝或赋值

ofstream out1,out2; out1 =out2;//错误

文件读取啥的,还有挺多语法。。不用还是生


第九章顺序容器

顺序容器(为程序员提供了控制元素存储和访问顺序的能力)都提供了快速顺序访问元素的能力,

vector(在尾部之外的位置插入或者删除不方便)

deque(双端队列,支持快速随机访问)

list(双向链表,插入和删除速度很快)

forward_list(单向链表,C++11)

array(固定大小数组。支持快速随机访问,不能添加或删除元素,C++11

string(和vector相似的容器,但是专门保存字符,随机访问快,尾部插入块)

vector<noDefault> v(10,init);//假定noDefault是一个没有默认构造函数的类型,则,必须有init

iterator(迭代器),const_iterator(可以读取元素,单不能修改)

a.swap(b) 等价 swap(a,b)交换

c.erase(args)删除args指定的元素

c.begin(),获取迭代器,c.cbegin(const_iterator)

只有顺序容器的构造函数才接受大小参数,关联容器并不支持。

assign(仅顺序容器)


push_back(),添加的是一个copy,对这个作任何改变,不会影响原来的对象。

C++11标准,引入了emplace_front、emplace、emplace_back,类似与利用参数直接构造对象,然后放入容器


at和下标运算符号,只适合string、vector、deque、array,返回的都是引用。

删除元素

c.pop_back()

c.erase(p)

vector的size是已经保存的数目,而capacity是最多保存多少元素。

每个vector内存不够了,都可以自己选择分配,有自己的分配策略,有的翻倍。

string有4个搜索操作,find,rfind(最后一次出现的位置),find_first_of(arg)(arg任何一个字符第一次出现的位置)

C++11,引入了数值转换,string s = to_string(i); double d = stod(s)。

三个顺序容器适配器(stack、queue、priority_queue),容器,迭代器,函数都有适配器。本质上,容器是一种机制。

例如stack,接受一个顺序容器(除了array和单链表),使其像stack。

栈stack:s.pop(),s.push(),s.emplace(args),s.top()

队列queue:q.pop(),q.front(),q.back();q.top(),q.push() priority_queue允许我们为队列建立优先级。


第十章 泛型算法(还得好好再看看,很多不理解)

大多数泛型算法,都定义在algoruthm中

迭代器令算法不依赖容器,但算法依赖于元素类型的操作

谓词:是一个可调用的表达式,其返回结果是一个能用作条件的值。

一元谓词:只接受单一参数和二元谓词。

C++11一个lambda表达式表示一个可调用的代码单元。我们可以将其理解为一个未命名的内联函数。

一个lambda具有一个返回类型、一个参数列表、和一个函数体

网上说的是lambda是匿名函数

[capture_block](parameters) mutable exception_specification->return_type{ function body }

exp:auto func1 = [](int i) { return i+4; };

for_each算法

C++11 bind std::bind(待绑定的函数对象/函数指针/成员函数指针,参数绑定值1,参数绑定值2,...,参数绑定值n);

(理解得不好)


第十一章 关联容器

关联容器支持高效的关键字查找和访问。

两个主要的关联容器,map和set。map是key-value;set则只包含一个关键字

标准库提供8个关联容器,map,set,multimap(可重复),multiset;(这4个还是有序的)

unordered_map,unordered_set,unordered_multimap,unordered_multiset(哈希的内部)

关联容器的排序,可以自己定义,方法如下图:


提一嘴pair类型,首先头文件是utility,有一种结构体的感觉,但是可以当做两个元素

exp:pari<string,string> author{"james","joyce"};

C++11,返回pair对象,可以return pairx;或者return {xxxx,yyyy};//列表初始化

key_type,value_type(关键字类型,是类型),对于set而言,两者一样

map用迭代器->first(key,而且是const,不能改),->second来获取元素

set没有first,且为const,不能改。

另外,我们通常不对关联容器使用泛型算法。

map添加元素4中办法:


删除,对于不重复的关键字

auto cnt = authors.erase("Barth,John");0或者1,0表示没有

假如删掉很多,cnt则可能是2,3。

删除貌似只能根据key或者迭代器,或者pair去删,不能根据value,find后再删

c[k],key为k,的元素

c.find(k),返回迭代器

无序容器,的哈希,貌似是用拉链法的,提到了桶,而且可以管理桶


第十二章动态内存

为了更容易(同时也更安全)地使用动态内存,提供了两种智能指针。区别于普通指针,他负责自动释放所指向的

对象。shared_ptr允许多个指针指向同一个对象;unique_ptr则独占所指向的对象。另外还有一个weak_ptr,是一种

弱引用。C++11,shared_ptr也是模板,因此需要提供额外信息,指向的类型

exp:shared_ptr<string> p 1;

make_shared函数,号称最安全的分配和使用动态内存的方法;此函数在动态内存分配一个对象并且初始化他,返回

指向此对象的一个shared_ptr。exp:shared_ptr<int> p3 = make_shared<int>(42)

当进行拷贝或赋值操作时,每个shared_ptr都会记录有多少个其他shared_ptr指向相同的对象。一个Note:


程序使用动态内存出于以下三种原因之一:程序不知道自己需要使用多少对象;程序不知道所需对象的准确类型;程

序需要在多个对象间共享数据。

由于智能指针的构造函数是explicit的,因此(不能将指针隐式转换)。因此必须使用直接初始化的形式:


用智能指针管理,假如代码出现异常,局部变量会被销毁(智能指针会被销毁),然后空间会被释放。而使用new和

delete,异常就不执行delete,就会内存泄漏。(一个不错exp)

另外,智能指针默认是用析构作为删除器,有些时候可以自己定义删除器:


智能指针陷阱:



然后谈谈 unique_ptr,得绑定在new返回的指针上,而且,必须直接初始化。这个指针不支持拷贝和赋值。

可以通过release或者reset转移所有权。可以执行特殊拷贝,exp:


另外,他也可以自定义删除器。

最后聊weak_ptr,感觉他的意义,他的意义就是,例如有A对象,然后呢,很多shared_ptr都指向某对象A,假如检测

shared_ptr是否是nullptr,是不能知道,对象A是否被处理了。因此需要weak_ptr,通过lock()提升自己,假如对象存在

就能提升成功,否则,提升失败,因此利用这个来检测对象是否真的没了。可以理解为一个辅助ptr

使用allocator类,允许分配和初始化分离意义用途比如,要用一大块区域,比如好多string,但是并不是所有的都会

用,new的话,所有的string都会初始化,很浪费,因此才会出现allocator。

unique_ptr可以直接管理动态数组。而shared_ptr,则不行。除非,定义自己的删除器。

allocator分配的内存是未构造的然后用construct构造对象。然后我们只能对真正构造了的元素进行destroy操作。

释放内存,靠deallocate(p,n)完成。


第十三章 拷贝控制

五类特殊函数

拷贝构造函数:第一个参数必须是一个引用

拷贝初始化发生的情况:将一个对象作为实参传递给一个非引用类型的形参。void f ( A a);

从一个返回类型为非引用类型的函数返回一个对象

用花括号列表初始化一个数组中的元素或一个聚合类中的成员

拷贝赋值函数:除了 = 这个事情

析构函数:首先执行函数体,然后销毁成员,按照初始化顺序的逆序销毁的。销毁内置类型成员,什么都不需要做。

认识到析构函数体自身并不直接销毁成员是非常重要的。成员是在析构函数体之后隐含的析构阶段被销毁的。

组织拷贝:C++11 xxx(const xxx&) = delete;阻止拷贝

来C++11之前,都是通过,设置为private,既不用系统的,也防止调用。

swap函数很重要

C++11,右值引用(&&),为了支持移动(这个不是很明白),左值持久;右值短暂

move函数,可以获得左值上的右值引用。

终于稍微理解了一点点。看个例子(点击打开链接):


简单来说,函数返回值是个类的话,再赋值给别人,需要的操作太多了,先拷贝,再删除之类的,move就能简化这个

事情

移动构造函数:让类具备这种move的效果,noexcept是C++11里的东西:


配套还要,移动迭代器,引用限定符,都是C++11的(这些都不是很明白)


第十四章 重载运算和类型转换(这章扫看)

重载的时候,递增,要同时重载前置和后置。两者的区别是:



explicit 显式的类型转换运算符


第十五章 面向对象程序设计(不是看的特别细)

核心是,数据抽象,继承和动态绑定

在C++语言中,当我们使用基类的引用(或指针)调用一个虚函时将发生动态绑定。

C++11提供了一种防止继承发生的方法,即在类名后跟一个关键字final(和java一样)

提供了overrider帮忙检查


也有回避虚函数机制:


重构:负责重新设计类的体系以便将操作和/或数据从一个类移动到另一个类中。

友元关系不会被继承


第十六章 模板和泛型编程(啥啥啥,不看了)

模板是C++泛型编程的基础,一个模板就是一个创建咧或函数的蓝图或者说是公式。


模板编译:当编译器遇到一个模板定义时,它并不生成代码。只有当我们实例化出模板的一个特定版本时,编译器

才会生成代码


第十七章 标准库特殊设施

tuple是类似pair的模板。每个pair的成员类型都不相同,但每个pair都恰好有两个成员。


第十八章用于大型程序的工具


第十九章特殊工具与技术

RTTI 运行时类型识别


2017年9月24日:

对于这本书,最大的体会就是:

让我全面的认识了很多C++11的特性,尤其对智能指针这块,有了很全面的认识。例如weak_ptr这个指针,乍一看

没有什么用处,但是,在判断对象是否真的被释放时候,weak_ptr有他独特的能力(lock升级)。以及能解决

shared_ptr彼此引用而导致无法正常释放内存的问题(其中一个shared_ptr换成weak_ptr)

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值