C++Primer笔记

**

第7章 类

**

7.1
	this是一个常量指针(int * const this),即它指向一个对象后就不可变,该对象可以不说常量。成员函
数加const后,会将this指针变成指向常量的常量指针(const int * const this)。常量对象只可以调用常量
成员

函数,因为常量对象是const的,需要一个指向常量的指针,不可以赋值给指向非常量的指针。

非成员函数是类接口的组成部分,则该函数声明应该与类声明(而非定义)在同一个头文件。P234

构造函数不可以是const的。创建一个const对象时,直到构造函数完成初始化后,对象才取得常量属性,构造函
数在const对象构造时可以改值。P235

7.2
mutable修饰的是可变数据成员,即使在const成员函数内也可以修改其值。P245

7.3
重载包括:(1)形参列表不同;
		 (2)成员函数是不是const->由调用对象睡吧睡吧const决定;
		 (3)虚函数覆盖->由调用类型决定 P248

前向声明的类在定义之前是不完全类型(可以定义指向该类型的指针或者引用,但不能创建该对象)P250

哪些函数可以定义成友员:(1)非成员函数;(2)其他类;(3)其他类的成员函数 P250

7.4
类内名字查找过程:(处理完类中全部声明后才会处理定义)
1.声明中使用的名字,如返回类型,参数列表中的名字在使用前应该定义好
2.成员函数体内的名字:
	(1)函数体内该名字之前的范围查找(和普通函数一样)
	(2)在类内找,此时类的所有成员都包含在内
	(3)在类外成员函数定义之前的范围找 P255

构造函数初始值列表:const和引用类型必须初始化;类类型且没有默认构造函数必须初始化 P258

7.5
int a;//默认初始化
int a=10;//值初始化
a=10; //赋值
对象被默认初始化或者值初始化时自动执行默认构造函数 P262

转换构造函数:构造函数只接受一个实参(隐式转换->两个类型之间自转换,只允许一步类型转换)P264

构造函数前加explicit来阻止隐式转换(只在类内声明时用,在类外定义时不用) P265

接受单参数const* char的string的构造函数不是explicit的,因此string a = "123"是对的。
接受一个容量参数的vector构造函数是explicit的 P266

强制类型转换:
(1)static_cast <new_type> (expression) 静态转换
	<1>允许基本数据类型的转换 ->与dynamic_cast比
	<2>上行转换安全(派生类指针转换成基类指针),下行转换不安全
	<3>没有安全检查   ->与dynamic_cast比
	<4>不允许无关类型的转换  ->与C风格的强制转换比
	<5>不能去掉const属性  ->与const_cast相比

(2)dynamic_cast <new_type> (expression) 动态转换
	要求new_type为指针或引用,其二是下行转换时要求基类是多态的(基类中包含至少一个虚函数)
	dynamic_cast可以识别出不安全的下行转换,但并不抛出异常,而是将转换的结果设置成null(空指针)
	下行转换是安全的,上行转换安全的情况:基类指针指向派生类,然后将该基类指针下行转换成派生类指
针,则这种下行转换是安全的,由于dynamic_cast是在运行时才确定类型的,因此可以检测出这种情况

(3)const_cast <new_type> (expression) 常量向非常量转换
	可以去掉常量的const属性,原常量还是const的,只是返回的是非const的

(4)reinterpret_cast <new_type> (expression) 重解释转换
	两个没有任何关系的类指针之间转换都可以用这个转换实现,最不安全。

参考:https://www.cnblogs.com/jerry19880126/archive/2012/08/14/2638192.html(写的很好)

7.6 
类的静态成员函数(static)不能是const的,不含有this指针
类外定义静态成员时,不重复使用static关键字。
必须在类外定义和初始化每个静态成员,一个静态数据成员只能定义一次 P270

friend,explicit,static,virtual在类外定义时要省略
inline,const在类外定义时不省略  P272

**

第十三章 拷贝控制

**

13.1
赋值运算符通常应该为指向左侧运算对象的引用
析构函数中首先执行函数体,然后销毁成员,按初始化顺序的逆序销毁。销毁类成员会调用类成员的析构函数,
内置类型会自动销毁。销毁一个指针不会delete其所在的对象。智能指针是有析构函数的。delete p(p指向一
个对象)会调用p的析构函数 P445

三五法则:P447
(1)有析构函数——>有拷贝构造和拷贝赋值函数(反之不一定)
(2)有拷贝构造函数<——>有拷贝赋值函数

=default ——>生成默认的版本

iostream等类阻止拷贝,避免多个对象写入或者读取相同的IO  P449
阻止拷贝和赋值可以用=delete
析构函数不可以是删除的(delete的)

13.3
std::swap() 调用的是标准库中的swap  P458
using std::swap;
swap() //调用的是在std空间中自己定义的swap

13.6 对象移动
IO类或者unique_ptr类不可以进行拷贝,但是可以移动

左值:变量(右值引用类型的变量也是左值P472),返回引用的函数
右值:字面常量,临时对象,返回非引用类型的函数 P471

左值引用可以绑定到左值上
常量左值引用可以绑定到右值上
右值引用可以绑定到右值上
右值引用特性:(1)只能绑定到一个将要销毁的对象上;(2)该对象没有其他用户

std::move()函数可以获得绑定到左值上的右值引用 P472 (不是using std::move;  move();)

移动操作一般都指明为noexcept的	P474
移动构造函数中的入参是右值引用类型,不是const的,因为它会改变原对象的值
(移动构造接受类型是 T&& ,拷贝构造函数类型是 const T&)
移动构造函数中一般要将源对象的指针设为nullptr,因为如果不这样设置有可能源对象析构后移动部分也就析构
了 P475

如果没有移动构造函数,那么右值将会被拷贝,即Foo&&可以被自动转换成const Foo&类型   P478

vector的push_back就有移动和拷贝两个版本,
	如果入参是左值就是拷贝,如:变量。如果是右值就是移动,如:字符串常量“123

**

第15章 面向对象程序设计

**

15.2
任何构造函数之外的非静态函数都可以是虚函数(构造函数不可以是虚函数,静态函数因为属于类不属于对象,
也不可以是虚函数) P528

override用于表明该成员函数覆盖了他继承的虚函数  P530

如果想将某个类用作基类,则该类必须已经定义而不能仅仅声明 P533

在类名后加final可以防止该类被继承  P533
在成员函数后加final可以防止该函数被派生类覆盖  P538

静态类型是变量声明时的类型或者表达式生成的类型,在编译时就可以确定;动态类型是变量或表达式表示的内
存中的对象类型,运行时才可以确定
如果变量不是指针或者引用,则静态类型和动态类型是一致的(只有基类的指针或引用的静态类型才可能与动态
类型不一致) P534
静态类型决定了哪些成员是可见的,比如基类指针指向派生类,指针的静态类型是基类的,因此该静态类型决定
了用指针不能访问派生类中的成员,但可访问积累中的成员,包括虚函数  P548

基类有一个或者多个虚函数,进行类型转换时可以用dynamic_cast,从而安全的进行从基类到派生类的转换
(会在运行时进行安全检查)。如果已经确定从基类到派生类的转换是安全的,则可以用static_cast P535

15.3
派生类到基类的类型转换只对指针或者引用有效。派生类必须是公有继承基类的时候,才可以用基类指针访问
接口  P536

通过基类指针或者引用调用虚函数时才会发生运行时绑定,即多态。用对象调用函数或者指针调用非虚函数都是
在变异期绑定的。  P537

派生类想要覆盖基类中的虚函数,则必须形参和返回类型与基类中的虚函数完全一致才可以,否则就认为是定义
了一个新函数(为了防止想覆盖虚函数但是定义错,给虚函数后面加override,当派生类中定义和基类虚函数不
一致就会报错,从而避免该情况)。这种情况有个例外,就是返回类型在基类中是基类的指针或引用,派生类中
返回类型是派生类的指针或引用,(派生类是公有继承自基类的)这种情况也算一致。    P537  P538

通过使用作用域运算符::可以回避虚函数机制,强制调用某个版本的虚函数,一般用于成员函数中(如果派生类
虚函数要调用它的基类版本虚函数时,如果不回避虚函数机制则会无限递归调用自己) P539

纯虚函数(虚函数不定义,后加=0)的类是抽象基类  P541

友元关系是不可继承的,各个类的友元只可访问自身的数据,不可访问派生类的数据  P545

派生类中可以通过using 声明来改变个别成员的访问属性(即private继承,但是用using可以改成public
属性)  P546

调用成员函数的过程如下:如P->fun()   P549
(1)确定P的静态类型
(2)在P的静态类型中查找fun()函数,如果没找到就到基类中取找
(3)找到fun()后进行类型检查,即看调用的参数类型与函数的参数类型是否一致,如果不一致则调用非法(这
一步决定了虚函数的形参列表必须一致,因为如果不一致,则到这一步的时候发现在静态类型(基类)中找到的
虚函数的参数与调用的参数不一致,则直接报错,而不会进行下一步了)
(4)判断P和fun(),如果P是指针或者引用,fun()是虚函数,则在运行时才会确定运行哪个版本的fun(),如
果不满足则在编译器决定运行哪个版本

上述过程中名字查找先于类型检查,比如:
基类A有fun(),子类B有fun(int),如果调用B.fun()会报错,因为在(2)步骤时在B中找到了fun(),而
在(3)步骤检查类型时发现不一致,报错

析构函数应该是虚函数,因为如果不是虚函数,则基类指针指向派生类,在delete基类指针的时候,会根据基类
指针的静态类型取调用基类的析构函数,而派生类的析构函数没有调用,那么派生类部分的资源无法回收  P552

默认情况下派生类会调用基类的默认构造函数/赋值运算符,如果想拷贝或者移动基类部分,应该在派生类构造
函数的初始值列表中显示调用基类的拷贝(移动)构造函数/赋值运算符   P555

默认,拷贝,移动构造函数不会被继承  P557

多态的类型: P574
(1)形参不同,返回类型可相同——>由实参决定调用哪个版本
(2)有const和无const——>由调用对象是否是常量对象来决定调用哪个版本
(3)指针/引用调用虚函数,形参和返回类型要一致——>由指向的对象决定调用哪个版本

重载,覆盖(重写),隐藏(重定义)的区别: P576
1.重载:
(1)必须在同一作用域;
(2)函数名相同,形参不同,返回类型可不同

2.覆盖(重写)
(1)子类重写父类的虚函数
(2)在不同的作用域内(基类和派生类)
(3)函数形参,返回类型要一致

3.隐藏(重定义)
(1)派生类隐藏与其同名的基类函数
(2)作用域不同(基类和派生类)
(3)函数名相同,形参可不同

18.3
虚继承(public virtual):令某个类做出声明,承诺共享它的基类。共享的基类对象为虚基类,无论继承多
少次都只有一个虚基类
对象  P717

**

第9章 顺序容器

**

9.1
顺序容器类型:vector,deque(双向队列),array, list, forward_list, string   P292

9.2
swap()操作不会对元素进行拷贝,删除或插入,在常数时间内完成。除string外,指向容器的迭代器,指针和
引用在swap后不会失效  P303

9.3
vector和string不支持push_front,emplace_front,pop_front
forward_list不支持push_back,emplace_back,他有自己特有的insert, emplace, erase操作

除了forward_list以外,其他的insert都是在迭代器之前插入元素,返回插入元素的迭代器(可以这样想,迭
代器指向A,在A前面插一个元素即插到了A的位置,后面的元素都向后移动一位,此时迭代器指向A)。向
vector,string,deque插入元素会使所有指向容易的迭代器失效?     P305

erase(p)操作会删除p所指的元素,返回该元素的后一个元素的迭代器(删除p的元素后,p之后的元素前移一
位,此时p指向的是删除元素的后一个元素)。删除deque中除首尾之外的其他元素会使所有迭代器失效,而
vector和string中会使删除点之后的迭代器失效 P311

给容器初始化时,放入的是元素的拷贝而不是元素本身   P306

每个顺序容器都有front()返回首元素引用,除了forward_list外都有back()返回尾元素的引用,这两个函数
都要求容器非空  P309

为了安全访问容器元素可以用at(),如果下标越界会报out_of_range异常,list和forward_list
没有at() 	P310

forward_list定义了insert_after(p)erase_after(p)操作,分别为给p之后的位置插入元素和删除p之后
的一个元素,还定义了一个before_begin()返回链表首元素之前一个不存在的元素

size(),capacity(),resieze(),reserve()区别:
https://www.cnblogs.com/fdd566/p/6598318.html
size()是元素个数,capacity()是容器大小
resize()是改变容器大小,同时也会对应填充默认对象
reserve()只改变容器大小,多出的部分是没有加元素的

迭代器失效情况:(如需重新分配空间则全部失效) P315
(1)vector/string
插入元素:插入之前迭代器有效,插入之后的失效(包括尾后迭代器)
删除元素:删除之前迭代器有效,删除之后迭代器无效(包括尾后迭代器)
(2)deque
插入元素:首插或者尾插不失效,其他位置插会导致所有的失效
删除元素:首删或者尾删不失效(尾删会导致尾后迭代器失效),其他位置删都失效
(3)list/forward_list
插和删都不失效,包括尾后迭代器也不失效

9.5
s.substr(pos,n) 返回s中从pos开始的n个字符组成的字符串
s.append(s2) 将s2加到s的尾部
string的find()操作,如果找到返回string::size_type类型的值,表示匹配的位置,如果没找到返回
string::npos的static成员,是一个const string::size_type类型的值

字符串与数值的转换:
to_string(i) 将i转换成string类型
stod(s)  将s转换成double类型
stoi(i)  将s转换成int类型
stof(i)  float类型

9.6
容器适配器:stack,queue,priority_queue
stack,queue基于deque实现,priority_queue基于vector实现

**

第11章 关联容器

**

map的元素是pair对象,first是key,second是value

map,set的关键字需要定义元素的比较方法,默认用<

自定义排序情况:  P379
1.sort()自定义排序cmp,cmp要求是函数
(1)cmp是自定义函数  
		sort(v.begin(),v.end(),cmp)->不加括号
(2)cmp是函数对象(重载了operate()sort(v.begin(),v.end(),cmp()) ->加括号
2.容器类自定义排序cmp,1)cmp是自定义函数
		set<string,decltype(cmp)*> my_set(cmp);
		decltype指出自定义操作的类型,后加*指出要使用函数类型的指针
(2)cmp是函数对象
		set<string,cmp> my_set;

11.3
关联容器中:key_type  关键字类型 mapped_type 关键字关联的类型    P381
		   value_type 对于set是关键字类型,对于map是pair<>类型

map的pair->first是const的(因为需要用键去排序) P381
set的迭代器是const的   P382

关联容器的insert()  P384
insert()的入参是一个和容器元素一样的pair类型,返回一个pair对象,first是指向插入元素的迭代器,
second表示是否插入成功

erase()   P387
erase(k)  k为关键字,返回删除的元素个数
erase(p)  p为迭代器,返回p之后的一个迭代器

访问map元素:[]at() 只适用于非const的map和unordered_map   P388

查找元素:  P389
(1find(k) k是关键字,返回指向对应元素的迭代器,没找到返回end()2count(k) 返回关键字为k的数目
(3)lower_bound和upper_bound只能用于有序容器
	lower_bound(k) 返回第一个小于等于k的迭代器
	upper_bound(k) 返回第一个大于k的迭代器

	equal_range() P391

关联容器需要定义<操作,无序容器需要定义哈希函数(hash<key_type>类型)和==运算符   P394

**

第12章 动态内存

**

1.智能指针

12.1
默认情况下,new分配空间失败会抛出bad_alloc异常     P408
定位new可以向new传入额外参数,如:
int *p = new(nothrow) int; //分配失败不会报错,会返回空指针

一般在类中的不同对象之间需要共享数据时,会用到动态内存

1. shared_ptr
初始化:
不能直接将原生指针赋值给shared_ptr,要用构造函数初始化或者reset()函数

shared_ptr<int> p(new int(5));  //第一种方式
shared_ptr<int> p = make_shared<int>(5);  //第二种方式
shared_ptr<int> p = new int(5);  //错误,shared_ptr的构造函数是explict的,禁止隐式转换
p = new int(5);  //也是错误的,要用reset函数
p = q; //q是另一个shared_ptr,正确的

成员函数:

p.reset()  // 将p置空,同时p指向对象的引用计数减1
p.reset(new int(5));
p.get();  //获得p的原生指针,尽量不要用

p.unique() 对象的指针为1时返回true,否则为false //不用记
p.use_count() 速度慢,共享对象的指针个数 //不用记

使用注意事项:
不要把原生指针和智能指针一其使用;

  1. 不要用相同的内置指针初始化多个shared_ptr;(也算把原生指针和智能指针一其使用的情况,应该用make_shared<T>()来初始化;
  2. 不要把原生指针和智能指针一其使用:
    (1)尽量不要用get,因为get会返回原生指针(比如get()返回的指针如果被delete了,则shared_ptr出错,或者用get()返回的指针初始化另一个shared_ptr,则其中一个计数为0被释放后,另一个出错——此时和1中类似)
2.   unique_ptr

某一时刻对象只能有一个unique_ptr,因此unique_ptr不支持拷贝,赋值

初始化方法:用原生指针初始化构造

unique_ptr<int> p(new int(5));
unique_ptr<int> p = new int(5);  //错误

成员函数:

p.release()    //p放弃对对象的控制权,p置空,返回对象的原生指针。该函数不能单独出现,很危险,因为会造成内存泄漏。p放弃控制权了,内存还没释放,又没有其他指针指向该内存,该内存无法释放。

解决方法:
(1)auto q = p.release();
(2)q.reset(p.release()); //q是另一个unique_ptr,reset不存在release的问题,如果q指向其他对象,则原来的内存会被释放
总之,要找到一个指针指向p原来的内存。

3.   weak_ptr

一般会将weak_ptr绑定到一个shared_ptr上,但不会改变shared_ptr的引用计数
初始化方式:

weak_ptr<int> p(q);   //q是一个shared_ptr

成员函数

p.reset();  //将p置空
p.lock();  //如果p指向对象不存在,则返回空的shared_ptr,如果存在则返回一个该对象的shared_ptr,一般用p前都需要用这个判断,因为weak_ptr的对象有可能不存在

————————————————————————————————————

二 allocator类

用于将内存分配和赋值分开。具体过程为:

allocator<T> p;
p.allocate(n);   //分配原始内存,不构造
p.construct();  //开始构造每一个对象
p.destroy();    //销毁每一个对象(如果对象有new空间,也一起删除)
p.deallocate();   //删除该内存
三  虚函数
  1. 重载,重写,重定义
    (1)重载:
    a. 同一个作用域内
    b. 两函数的函数名相同,但是参数不能完全相同,可以是参数类型不同或者是参数个数不同,至于返回值,不影响重载

(2)重写(覆盖)
a. 不同的范围,分别位于基类和派生类中
b. 是虚函数
c. 子类中对应的虚函数函数名必须相同,参数列表必须相同,返回值可以不相同,但是必须是父子关系的指针或引用。

(3)重定义(隐藏)
a. 子类实现了一个和父类名字一样的函数,子类的函数就把父类的同名函数隐藏了(只关注函数名,和参数与返回值无关)(即使是虚函数,若参数不同也会隐藏)

class A
{
	public:
		virtual int func();
}

class B:public A
{
	int func(int);
}
B b;
A* p=b;//基类指针指向派生类
p->func();  //调用的是A::func()

可以这样记忆:
基类的成员在子类中要么不管了(此时它还存在于子类中),要么重写(此时相当于子类基类函数都存在),要么重定义(此时只存在子类的函数)
重写是基类和子类的函数都存在,当用指针调用函数时,根据情况来选择用哪个;
重定义是子类将基类的函数完全隐藏,此时不管怎么调用都是子类的那个函数。
(这部分应该是理解错了,看前面的就行)

  1. 虚函数
    虚函数要求在子类中的函数名,形参必须与基类完全一致,至于返回值要近似一致
class A {virtual  A* func();}
class B:public A
{B* func();}  //返回值近似一致,要求从B到A的类型转换是合法的

多态性要求:
(1)必须是基类指针或者引用调用函数(不可以是对象直接调用,若是对象直接调用则会调用对象所对应的那个函数)
(2)必须是虚函数(不可以是普通的函数,如果是普通函数而指针是基类指针,因此会调用基类对应的函数)

  1. 子类的作用于是嵌套在父类的作用域之内的,当在子类中找不到对应变量或者成员时会去父类中查找

**

第10章 泛型算法(只含部分内容)

**

10.3 lambda表达式
可调用对象:函数,函数指针,重载了函数调用运算符的类,lambda表达式,bind创建的对象 P346,511
重载了函数调用运算符的类和lambda表达式都是函数对象  P507

编写了一个lambda表达式后,编译器将该表达式翻译成一个未命名类的未命名对象,该类中含有一个重载的函
数调用运算符  P508

默认情况下,lambda不可以改变它捕获的变量,即其产生的类中函数调用运算符是一个const成员函数  P508

要想改变值捕获的变量,需要在函数体参数列表后加mutable
引用捕获是否可以改取决于引用指向的是const还是非const  P353

lambda产生的类不含默认的构造函数,赋值运算符以及默认的析构函数,是否含有默认的拷贝/移动构造函数要
根据捕获的成员类型决定  P509

lambda表达式格式:
[]()->int {};
[]是捕获列表,()是形参列表,->int是尾置返回类型,{}是函数体。其中只有[]{}是必须的,其他可以
省略

lambda未指定返回类型时,如果函数体只包含return一个语句,则进行类型推断;如果还包含其他语句,则返
回类型为void   P347

lambda表达式如果想在函数体中使用外层函数中的局部变量,则需要将该变量包含进捕获列表里.但是lambda
可以直接使用定义在外层函数之外的名字,不需要加入捕获列表(这里外层函数是lambda表达式所在的
函数)。即捕获列表中加入局部非static变量,不加入局部static变量和所在函数外的名字  P348,349

捕获类型:  P350
(1)值捕获:采用值捕获的前提是变量可以拷贝。被捕获的变量的值是在创建lambda时拷贝,而不是调用时
	 拷贝。
(2)引用捕获:前面加&,采用引用捕获需要确保被引对象在lambda执行的时候存在。
(3)隐式捕获:=值捕获,&引用捕获,捕获列表不用写具体变量名,会自动推导所需的名字

lambda返回类型需要用尾置返回类型->    P353

10.4 迭代器
分:1: P357
(1)插入迭代器
front_inserter(), back_inserter(), inserter()2)流迭代器
istream_iterator<int> p;3)反向迭代器
rbegin(), rend()4)移动迭代器

分类2: P365
输入迭代器
输出迭代器
前向迭代器
双向迭代器
随机访问迭代器

**

第14章 重载运算与类型转换(只包含一部分)

**

14.8
function<T> f

function<int(int,int)>  P512
声明了一个function类型,接受两个int,返回一个int的可调用对象

function<int(int,int)> f1 = add;    //函数指针,函数名
function<int(int,int)> f2 = add();  //函数对象,重载()
function<int(int,int)> f3 = [](){}; //lambda表达式
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值