两万字总结《C++ Primer》要点

Posted C语言与CPP编程

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了两万字总结《C++ Primer》要点相关的知识,希望对你有一定的参考价值。

 本文为《C++ Primer 中文版(第五版)》1-16章阅读要点总结。原书更为详细,本文仅作学习交流使用。

第一章 开始

1.1 编写一个简单的C++程序

int main()
{
    return 0;
}

每个C++程序都包含一个或多个函数,其中一个必须命名为main.

1.2 初识输入输出

对象用途
cin标准输入
cout标准输出
cerr标准错误
clog输出运行时的一般性消息

1.3 注释简介

两种:

单行注释://

界定符:/* 和 */

1.4 控制流

while;for;if;

第二章 变量和基本类型

P30-P71

数据类型是程序的基础。C++语言支持广泛的数据类型。

基本内置类型

算术类型

类型最小尺寸
bool未定义
char8位
w_char_t16位
char16_t16位
char32_t32位
short16位
int16位
long32位
long long64位
float6位有效数字
double10位有效数字
long double10位有效数字

类型转换

不要混用符号类型和无符号类型。

变量

变量定义

(1)基本形式:

类型说明符,随后紧跟着一个或者多个变量名组成的列表,其中变量名以逗号分隔,最后以分号结束。

(2)初始值

在C++中,初始化和赋值是2个完全不同的操作。初始化的含义是创建变量的时候赋予一个初始值,而赋值的含义是把对象的当前值擦除,用一个新值来替代。两者区别很小。

(3)列表初始化

用花括号来初始化变量的方式,称为列表初始化。

(4)默认初始化

如果定义变量没有指定初始值,则变量被默认初始化。

::: tip

例外情况:

定义在函数体内部的内置类型变量将不被初始化,其值未定义。

建议初始化每个内置类型的变量。:::

变量声明和定义的关系

变量声明:规定了变量的类型和名字。

变量定义:除声明之外,还需要申请存储空间。

如果想声明一个变量,而非定义它,需要使用extern关键词。

extern int i;    // 声明i而非定义i
int j;           // 声明并定义j

::: tip变量只能被定义一次,但可以被多次声明。:::

名字的作用域

作用域:C++中大多数作用域都用花括号分隔。

作用域中一旦声明了某个名字,它所嵌套的所有作用域都能访问该名字。同时,允许在内层作用域中重新定义外层作用域中有的名字。

::: warning如果函数有可能用到某全局变量,则不宜再定义一个同名的局部变量。:::

复合类型

定义:复合类型是基于其他类型定义的类型。

引用

引用:为对象起另外一个名字。

::: warning引用必须被初始化。引用本身不是对象,所以不能定义引用的引用。引用要和绑定的对象严格匹配。引用类型的初始值,必须是一个对象。:::

指针

指针:本身就是一个对象。允许对指针赋值和拷贝。指针无须在定义的时候赋值。

(1)利用指针访问对象

如果指针指向了一个对象,则允许使用解引用符(*)来访问该对象。

(2)void* 指针

理解复合类型的声明

(1)指向指针的指针

** 表示指向指针的指针

*** 表示指向指针的指针的指针

(2)指向指针的引用

不能定义指向引用的指针。但指针是对象,所以存在对指针的引用。

const限定符

定义:const用于定义一个变量,它的值不能被改变。const对象必须初始化。

::: tip

默认状态下,const对象仅在文件内有效。当多个文件出现了同名的const变量时,等同于在不同文件中分别定义了独立的变量。

如果想让const变量在文件间共享,则使用extern修饰。

:::

(1)const的引用

允许为一个常量引用绑定非常量的对象、字面值,甚至是个一般表达式。

一般,引用的类型必须与其所引用对象的类型一致,特殊情况是表达式。

(2)指针和const

弄清楚类型,可以从右边往左边阅读。

(3)顶层const

top-level const 表示指针本身是个常量

low-level const表示指针所指的对象是一个常量。

(4)constexpr和常量表达式

C++新标准规定,允许将变量声明为constexpr类型以便由编译器来验证变量的值是否是一个常量表达式。

处理类型

类型别名

两种方法用于定义类型别名:

(1)使用关键词typedef

typedef double wages; //wages是double的同义词
typedef wages *p; // p是double*的同义词

(2)别名声明

using SI = Sales_item;  // SI是Sales_item的同义词

auto类型说明符:让编译器通过初始值来推算变量的类型。

decltype类型指示符:选择并返回操作符的数据类型。只得到类型,不实际计算表达式的值。

自定义数据结构

(1)类

数据结构是把一组相关的数据元素组织起来,然后使用它们的策略和方法。

类一般不定义在函数体内,为了确保各个文件中类的定义一致,类通常被定义在头文件中,而且类所在头文件的名字应该与类的名字一样。

头文件通常包含那些被定义一次的实体。

(2)预处理器

#ifndef SALES_DATA_H
#define SALES_DATA_H
#endif

一般把预处理变量的名字全部大写。

术语

空指针 :值为0的指针,空指针合法但是不指向任何对象。nullPtr是表示空指针的字面值常量。

void*:可以指向任意非常量的指针类型,不能执行解引用操作。

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

P74-P118

string表示可变长的字符序列,vector存放的是某种给定类型对象的可变长序列。

命名空间的 using 声明

头文件不应包含using声明。

using namespace:name;

标准库类型 string

#include <string>
using namespace std;

(1)定义和初始化

string s1;
sting s2(s1);
string s3("value");
string s3 = "value";
string s4(n, 'c');

(2)string对象的操作

s.empty();      // 判空
s.size();       // 字符个数
s[n];           // s中第n个字符的引用
s1+s2;          // s1和s2连接
<,<=,>,>=       // 比较

::: warning

标准局允许把字面值和字符串字面值转换成string对象。字面值和string是不同的类型。

:::

(3)处理string对象中的字符

::: tipC++程序的头文件应该使用cname,而不应该使用name.h的形式:::

遍历给定序列中的每个值执行某种操作

for (declaration : expression)
    statement

标准库类型 vector

标准库vector表示对象的集合,其中所有对象的类型都相同。

vector是一个类模板,而不是类型。

(1)定义和初始化vector对象

vector<T> v1;
vector<T> v2(v1);
vector<T> v2 = v1;
vector<T> v3(n, val);
vector<T> v4(n);
vector<T> v5{a,b,c...}
vecrot<T> v5={a,b,c...}

如果用圆括号,那么提供的值是用来构造vector对象的。

如果用花括号,则是使用列表初始化该vector对象。

(2)向vector对象添加元素

先定义一个空的vector对象,在运行的时候使用push_back向其中添加具体指。

(3)其他vector操作

v.empty();
v.size();
v.push_back(t);
v[n];

::: warning

只能对确认已存在的元素执行下标操作。

:::

迭代器介绍

迭代器运算符

*iter            // 解引用,返回引用
iter->mem        // 等价于  (*iter).mem
++iter
--iter
iter1 == iter2
iter1 != iter2
iter + n
iter - n
iter += n
iter -= n
iter1 - iter2     // 两个迭代器相减的结果是它们之间的距离
>, >=, <, <=      // 位置比较

::: warning

凡是使用了迭代器的循环体,都不能向迭代器所属的容器添加元素。

:::

数组

(1)数组、指针

使用数组下标的时候,通常将其定义为size_t类型。

::: warning

定义数组必须指定数组的类型,不允许用auto推断。

不存在引用的数组。

如果两个指针分别指向不相关的对象,则不能进行对这2个指针进行比较。

:::

多维数组

多维数组实际上是数组的数组。

size_t cnt = 0;
for(auto &row : a)
  for (auto &col : row){
    col = cnt;
    ++cnt;
  }
int *ip[4];    // 整型指针的数组
int (*ip)[4];  // 指向含有4个整数的数组

术语

begin string和vector的成员,返回指向第一个元素的迭代器。也是一个标准库函数,输入一个数组,返回指向该数组首元素的指针。

end string和vector的成员,返回一个尾后迭代器。也是一个标准库函数,输入一个数组,返回指向该数组尾元素的下一个位置的指针。

第四章 表达式

P120-P151

4.1 基础

重载运算符:为已经存在的运算符赋予了另外一层含义。

左值、右值

当一个对象用作右值得时候,用的是对象的值(内容)。

当对象被用作左值得时候,用的是对象的身份(在内存中的位置)。

4.2 算术运算符

%:参与取余运算的运算对象必须是整数类型。

4.3 逻辑和关系运算符

运算符
!
<
<=
>
>=
==
!=
&&
||

&& 运算符和 || 运算符都是先求左侧运算对象的值再求右侧运算对象的值。   

::: warning

进行比较运算的时候,除非比较的对象是bool类型,否则不要使用布尔字面值true,false作为运算对象。

:::

4.4 赋值运算符

赋值运算符满足右结合律。

不要混淆相等运算符和赋值运算符

if (i = j)
if (i == j)

4.5 递增和递减运算符

递增运算符 ++

递减运算符 --

4.6 成员访问运算符

点运算符和箭头运算符

n = (*p).size();
n = p->size();

4.7 条件运算符

condition ? expression1 : expression2;

4.8 位运算符

运算符功能用法备注
~位求反~expr1置为0,0置为1
<<左移expr << expr2在右侧插入值位0的二进制位
>>右移expr1 >> expr2
&位与expr1 & expr2对应位置都是1,则结果1,否则为0。
^位异或expr1 ^ expr2对应位置有且只有1个为1,则结果是1,否则为0。
|位或expr1 | expr2对应位置至少有1个位1,则结果是1,否则为0。

4.9 sizeof运算符

sizeof运算符返回一条表达式或一个类型名字所占的字节数,其所得值是一个size_t类型,是一个常量表达式。

sizeof (type)
sizeof expr

4.10 逗号运算符

逗号运算符含有两个运算对象,按照从左向右的顺序依次求值。

4.11 类型转换

隐式转换

显式转换

命名的强制类型转换

cast-name<type>(expression)
// cast-name是static_cast,dynamic_cast,const_cast,reinterpret_cast

::: tip

由于强制类型转换干扰了正常的类型检查,因此建议避免强制类型转换。

:::

4.12 运算符优先级表

第五章 语句

P154-P178

5.1 简单语句

(1)空语句

;    // 空语句

(2)复合语句

复合语句是指用花括号括起来的(可能为空的)语句和声明的序列,复合语句也被称作块(block)。

{}

5.2 语句作用域

定义在控制结构当中的变量只在相应语句的内部可见,一旦语句结束,变量就超出其作用范围。

5.3 条件语句

(1)if 语句

(2)switch 语句

case关键字和它对应的值一起被称为case标签。

case标签必须是整形常量表达式。

如果某个case标签匹配成功,将从该标签开始往后顺序执行所有case分支,除非程序显示的中断了这一过程。

dedault 标签:如果没有任何一个case标签能匹配上switch表达式的值,程序将执行紧跟在default标签后面的语句。

5.4 迭代语句

(1)while 语句

while (condition)
      statement

(2)传统 for 语句

for (initializar; condition; expression)
    statement

for 语句中定义的对象只在for循环体内可见。

(3)范围 for 语句

for (declaration : expression)
    statement

(4)do while 语句

do 
  statement
while (condition)

5.5 跳转语句

break

break只能出现在迭代语句或者switch语句内部。仅限于终止离它最近的语句,然后从这些语句之后的第一条语句开始执行。

continue

continue语句终止最近的循环中的当前迭代并立即开始下一次迭代。

goto

goto的作用是从goto语句无条件跳转到同一函数内的另一条语句。

容易造成控制流混乱,应禁止使用。

return

5.6 try语句块和异常处理

C++中异常处理包括:throw表达式、try语句块。

try和catch,将一段可能抛出异常的语句序列括在花括号里构成try语句块。catch子句负责处理代码抛出的异常。

throw表达式语句,终止函数的执行。抛出一个异常,并把控制权转移到能处理该异常的最近的catch字句。

第六章 函数

P182-P225

6.1 函数基础

(1)形参和实参:

实参的类型必须与对应的形参类型匹配。

函数的调用规定实参数量应与形参数量一致。

(2)局部对象

形参和参数体内部定义的变量统称为局部变量,它们对函数而言是"局部"的,仅在函数的作用域内可见,同时局部变量还会隐藏外层作用域中同名的其他变量。

自动对象:只存在于块执行期间的对象。

局部静态对象:在程序的执行路径第一次经过对象定义语句时候进行初始化,并且直到程序终止才会被销毁。

size_t count_calls()
{
  static size_t ctr = 0;
  return ++ctr;
}

(3)函数声明

函数的三要素:(返回类型、函数名、形参类型)。

函数可被声明多次,但只能被定义一次。

(4)分离式编译

分离式编译允许把程序分割到几个文件中去,每个文件独立编译。

编译->链接

6.2 参数传递

当形参是引用类型,这时它对应的实参被引用传递或者函数被传引用调用。

当实参被拷贝给形参,这样的实参被值传递或者函数被传值调用。

(1)传值参数

(2)被引用传参

(3)const形参和实参

(4)数组形参

为函数传递一个数组时,实际上传递的是指向数组首元素的指针。

void print(const int*);
void pring(const int[]);
void print(const int[10]);
// 以上三个函数等价

数组引用实参:f(int (&arr)[10])

int *matrix[10];   // 10个指针构成的数组
int (*matrix)[10]; // 指向含有10个整数的数组的指针

(5)含有可变形参的数组

initializer_list

for err_msg(initializer_list<string> li)

6.3 返回类型和return语句

2种:无返回值函数和右返回值函数。

return;
return expression;

函数完成后,它所占用的存储空间也会随着被释放掉。

::: warning

返回局部对象的引用是错误的;返回局部对象的指针也是错误的。

:::

6.4 函数重载

重载函数:同一作用域内的几个函数名字相同但形参列表不通,我们称之为重载函数。(overloaded)。

不允许2个函数除了返回类型外其他所有的要素都相同。

重载与作用域

如果在内存作用域中声明名字,它将隐藏外层作用域中声明的同名实体。

6.5 特殊用途语言特性

(1)默认实参

函数调用时,实参按其位置解析,默认实参负责填补函数调用缺少的尾部实参。

typedef string::size_type sz;
string screen(sz ht = 24, sz wid = 80, char background = ' ');

::: tip

当设计含有默认实参的函数时,需要合理设置形参的顺序。一旦某个形参被赋予了默认值,它后面的所有形参都必须有默认值。

:::

(2)内联函数

使用关键词inline来声明内联函数。

内联用于优化规模较小,流程直接,频繁调用的函数。

(3)constexpr函数

constexpr函数是指能用于常量表达式的函数。

6.6 函数匹配

Step1:确定候选函数和可选函数。

Step2:寻找最佳匹配。

6.7 函数指针

函数指针指向的是函数而非对象。

void useBigger (const string &s1, const string &s2, bool pf(const string &, const string &));
等价于
void useBigger (const string &s1, const string &s2, bool (*pf)(const string &, const string &));

第七章 类

P228-P273

类的基本思想是数据抽象和封装。

抽象是一种依赖于接口和实现分离的编程技术。

封装实现了类的接口和实现的分离。

7.1 定义抽象数据类型

(1)this

任何对类成员的直接访问都被看作this的隐式引用。

std::string isbn() const {return bookNo;}

等价于

std::string isbn() const {return this->bookNo;}

(2)在类的外部定义成员函数

类外部定义的成员的名字必须包含它所属的类名。

double Sales_data::avg_price() const {
  if (units_sol)
    return revenue/units_sols;
  else
    return 0;
}

(3)构造函数

定义:类通过一个或几个特殊的成员函数来控制其对象的初始化过程,这些函数叫做构造函数。

构造函数没有返回类型;

构造函数的名字和类名相同。

类通过一个特殊的构造函数来控制默认初始化过程,这个函数叫做默认构造函数

编译器创建的构造函数被称为合成的默认构造函数

::: tip

只有当类没有声明任何构造函数的时,编译器才会自动的生成默认构造函数。

一旦我们定义了一些其他的构造函数,除非我们再定义一个默认的构造函数,否则类将没有默认构造函数

:::

7.2 访问控制与封装

(1)访问控制

说明符用途
public使用public定义的成员,在整个程序内可被访问,public成员定义类的接口。
private使用private定义的成员可以被类的成员函数访问,但是不能被使用该类的代码访问,private部分封装了类的实现细节。

(2)友元

类可以允许其他类或者函数访问它的非公有成员,方法是令其他类或者函数成为它的友元。

以friend关键字标识。

友元不是类的成员,不受访问控制级别的约束。

::: tip

友元的声明仅仅制定了访问的权限,而非通常意义的函数声明。必须在友元之外再专门对函数进行一次声明。

:::

// Sales_data.h


class Sales_data {
friend Sales_data add(const Sales_data&, const Sales_data&);
friend std::ostream &print(std::ostream&, const Sales_data&);
friend std::istream &read(std::istream&, Sales_data&);
}


// nonmember Sales_data interface functions
Sales_data add(const Sales_data&, const Sales_data&);
std::ostream &print(std::ostream&, const Sales_data&);
std::istream &read(std::istream&, Sales_data&);


//Sales_data.cpp


Sales_data 
add(const Sales_data &lhs, const Sales_data &rhs)
{
  Sales_data sum = lhs;  // copy data members from lhs into sum
  sum.combine(rhs);      // add data members from rhs into sum
  return sum;
}


// transactions contain ISBN, number of copies sold, and sales price
istream&
read(istream &is, Sales_data &item)
{
  double price = 0;
  is >> item.bookNo >> item.units_sold >> price;
  item.revenue = price * item.units_sold;
  return is;
}


ostream&
print(ostream &os, const Sales_data &item)
{
  os << item.isbn() << " " << item.units_sold << " " 
     << item.revenue << " " << item.avg_price();
  return os;
}

7.3 类的其他特性

(1)重载成员变量

Screen myScrren;
char ch = myScreen.get();
ch = myScreen.get(0,0);

(2)类数据成员的初始化

类内初始值必须使用=或者{}的初始化形式。

class Window_mgr{
private:
    std::vector<Screen> screens{Screen(24, 80, ' ')};
}

(3)基于const的重载

class Screen {
public:
  // display overloaded on whether the object is const or not
    Screen &display(std::ostream &os) 
{ do_display(os); return *this; }
    const Screen &display(std::ostream &os) const
{ do_display(os); return *this; }
}

当某个对象调用display的时候,该对象是否是const决定了应该调用display的哪个版本。

(3)类类型

对于一个类来说,在我们创建他的对象之前该类必须被定义过,而不能仅被声明。

(4)友元

友元类

如果一个类指定了友元类,则友元类的成员函数可以访问此类包括非公有成员在内的所有成员。

class Screen {
  // Window_mgr的成员可以访问Screen类的私有部分
  friend class Window_mgr;
}

令成员函数作为友元

class Screen {
  // Window_mgr::clear必须在Screen类之前被声明
  friend void Window_mgr::clear(ScreenIndex);
}

7.4 类的作用域

一个类就是一个作用域。

7.5 构造函数再探

(1)构造函数的初始值有时必不可少

::: tip

如果成员是const、引用,或者属于某种未提供默认构造函数的类类型化。我们必须通过构造函数初始值列表为这些成员提供初值。

:::

class ConstRef{
public:
  ConstRef (int i);
private:
  int i;
  const int ci;
  int &ri;
};


ConstRef:ConstRef(int ii) : i(ii), ci(ii), ri(i){  }

(2)成员初始化的顺序

成员初始化的顺序与它们在类定义中出现 的顺序一致。P259

(3)委托构造函数

使用它所述类的其他构造函数执行它自己的初始化过程。

(4)如果去抑制构造函数定义的隐式转换?

在类内声明构造函数的时候使用explicit关键字。

7.6 类的静态成员

(1)声明静态成员

在成员的声明之前加上关键词static。

类的静态成员存在于任何对象之外,对象中不包含任何与静态成员有关的数据。

(2)使用类的静态成员

double r;
r = Account::rate();

小结

类有两项基本能力:

一是数据数据抽象,即定义数据成员和函数成员的能力;

二是封装,即保护类的成员不被随意访问的能力。

第八章 IO库

P278-P290

C++语言不直接处理输入输出,而是通过一组定义在标准库中的类型来处理IO。

  • iostream处理控制台IO

  • fstream处理命名文件IO

  • stringstream完成内存string的IO

ifstream和istringstream继承自istream

ofstream和ostringstream继承自ostream

8.1 IO类

(1)IO对象无拷贝或复制。

进行IO操作的函数通常以引用方式传递和返回流。

(2)刷新输出缓冲区

flush刷新缓冲区,但不输出任何额外的字符;

ends向缓冲区插入一个空字符,然后刷新缓冲区。

8.2 文件输入输出

作用
ifstream从一个给定文件读取数据
ofstream从一个给定文件写入数据
fstream读写给定文件

8.3 string流

作用
istringstream从string读取数据
ostringstream向string写入数据
stringstream既可从string读数据也可以向string写数据
  // will hold a line and word from input, respectively
  string line, word;


  // will hold all the records from the input
  vector<PersonInfo> people;


  // read the input a line at a time until end-of-file (or other error)
  while (getline(is, line)) {       
    PersonInfo info;            // object to hold this record's data
      istringstream record(line); // bind record to the line we just read
    record >> info.name;        // read the name
      while (record >> word)      // read the phone numbers 
      info.phones.push_back(word);  // and store them
    people.push_back(info); // append this record to people
  }




  // for each entry in people
  for (vector<PersonInfo>::const_iterator entry = people.begin();
        entry != people.end(); ++entry) {    
    ostringstream formatted, badNums; // objects created on each loop


    // for each number
    for (vector<string>::const_iterator nums = entry->phones.begin();
        nums != entry->phones.end(); ++nums) {  
      if (!valid(*nums)) {           
        badNums << " " << *nums;  // string in badNums
      } else                        
        // ``writes'' to formatted's string
        formatted << " " << format(*nums); 
    }
    if (badNums.str().empty())      // there were no bad numbers
      os << entry->name << " "    // print the name 
         << formatted.str() << endl; // and reformatted numbers 
    else                   // otherwise, print the name and bad numbers
      cerr << "input error: " << entry->name 
           << " invalid number(s) " << badNums.str() << endl;
  }

第九章 顺序容器

P292-P332

顺序容器为程序员提供了控制元素存储和访问顺序的能力。

9.1 顺序容器概述

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

9.2 容器库概述

一般,每个容器都定义在一个头文件中。

容器均定义为模板类。

类型别名
iterator此容器类型的迭代器类型
const_iterator可以读取元素,但不能修改元素的迭代器类型
size_type无符号整数类型,足够保存此种容器类型最大可能容器的大小
difference_type带符号整数类型,足够保存两个迭代器之间的距离
value_type元素类型
reference元素的左值诶性:与value_type&含义相同
const_reference元素的const左值类型(即,const value_type&)
构造函数
C c;默认构造函数,构造空容器
C c1(c2)构造c2的拷贝c1
C c(b, e)构造c,将迭代器b和e指定的范围内的元素拷贝到c(array不支持)
C c{a, b, c...}列表初始化c
赋值与swap
c1=c2将c1中的元素替换为c2中元素
c1 = {a, b, c...}将c1中的元素替换为列表中元素(不适用于array)
a.swap(b)交换a和b的元素
swap(a, b)与a.swap(b)等价
大小
c.size()c中元素的数组(不支持forward_list)
c.max_size()c中可保存的最大元素数目
c.empty()若c中存储了元素,返回false,否则返回true
添加/删除元素(不适用于array)
c.insert(args)将args中的元素拷贝进c
c.emplace(inits)使用inits构造c中的一个元素
c.erase(args)删除args指定的元素
c.clear()删除c中的所有元素,返回void
关系运算符
==, !=所有容器都支持相等(不等运算符)
<,<=,>,>=关系运算符(无序关联容器不支持)
获取迭代器
c.begin(), c.end()返回指向c的首元素和尾元素之后位置的迭代器
c.cbengin(),c.cend()返回const_iterator
反向容器的额外成员(不支持forward_list)
reverse_iterator按逆序寻址元素的迭代器
const_reverse_iterator不能修改元素的逆序迭代器
c.rbegin(), c.rend()返回指向c的尾元素和首元素之前位置的迭代器
c.crbegin(), c.crend()返回const_reverse_iterator

(1)迭代器

标准库的迭代器允许我们访问容器中的元素,所有迭代器都是通过解引用运算符来实现这个操作。

一个迭代器返回由一对迭代器表示,两个迭代器分别指向同一个容器中的元素或者是尾元素之后的位置。它们标记了容器中元素的一个范围。

左闭合区间:[begin, end)

while (begin !=end){
    *begin = val;
    ++begin;
}

(2)容器类型成员

见概述

通过别名,可以在不了解容器中元素类型的情况下使用它。

(3)begin和end成员

begin是容器中第一个元素的迭代器

end是容器尾元素之后位置的迭代器

(4)容器定义和初始化

P290

C c;            // 默认构造函数
C c1(c2)
C c1=c2
C c{a,b,c...}   // 列表初始化
C c={a,b,c...}
C c(b,e)        // c初始化为迭代器b和e指定范围中的元素的拷贝
// 只有顺序容器(不包括array)的构造函数才能接受大小参数
C seq(n)
C seq(n,t)

将一个容器初始化为另一个容器的拷贝:

当将一个容器初始化为另一个容器的拷贝时,两个容器的容器类型和元素类型都必须相同。

不过,当传递迭代器参数来拷贝一个范围时,就不要求容器类型相同,只要能将要拷贝的元素转换为要初始化的容器的元素类型即可。

标注库array具有固定大小:

不能对内置数组类型进行拷贝或对象赋值操作,但array并无此限制。P301

(5)赋值与swap

arrray类型不允许用花括号包围的值列表进行赋值。

array<int, 10> a2={0}; //所有元素均为0
s2={0};  // 错误!

seq.assign(b,e)   // 将seq中的元素替换为迭代器b和e所表示的范围中的元素。迭代器b和e不能指向seq中的元素。

swap用于交换2个相同类型容器的内容。调用swap之后,两个容器中的元素将交换。

(6)容器大小操作

size 返回容器中元素的数目

empty 当size为0返回布尔值true,否则返回false

max_size 返回一个大于或等于该类型容器所能容纳的最大元素数的值

(7)关系运算符

关系运算符左右两边的元素符对象必须是相同类型的容器。

::: tip

只有当元素类型也定义了相应的比较运算符,才可以使用关系元素安抚来比较两个容器

:::

9.3 顺序容器操作

(1)向顺序容器添加元素

表格P305

使用push_back:追加到容器尾部

使用push_front:插入到容器头部

在容器中的特定位置添加元素:使用insert

vector <string> svec;
svec.insert(svec.begin(), "Hello!");

插入范围内元素:使用insert

使用emplace操作:

emplace_front、emplace和emplace_back分别对应push_front、insert和push_back。

emplace函数直接在容器中构造函数,不是拷贝。

(2)访问元素

P309

注意end是指向的是容器尾元素之后的元素。

在顺序容器中访问元素的操作
c.back()返回c中尾元素的引用。若c为空,函数行为未定义
c.front()返回c中首元素的引用。若c为空,哈数行为未定义
c[n]返回c中下标为n的元素的引用,n是一个无符号整数。若n>=size(),则函数行为未定义
c.at[n]返回下标为n的元素的引用。如果下标越界,则抛出out_of_range异常

(3)删除元素

顺序容器的删除操作
c.pop_back()删除c中尾元素。若c为空,则函数行为未定义。返回返回void
c.pop_front()删除c中首元素。若c为空,则函数行为未定义。返回void
c.erase(p)删除迭代器p所指定的元素,返回一个指向被删除元素之后元素的迭代器,如p指向尾元素,则返回尾后(off-the-end)迭代器。若p是尾后迭代器,则函数行为未定义
c.erase(b, e)删除迭代器b和e所指定范围内的元素。返回一个指向最后一个被删除元素之后元素的迭代器。若e本身就是尾后迭代器,则函数也返回尾后迭代器
c.claer()删除c中的所有元素。返回void

(4)特殊的forwar_list操作

P313

befor_begin();cbefore_begin();insert_after;emplace_after;erase_after;

(5)改变容器大小

reseize用于扩大或者缩小容器。

resize操作接受一个可选的元素值参数,用来初始化添加到容器内的元素。

如果容器保存的是类类型元素,且resize向容器中添加新元素,则必须提供初始值,或者元素类型必须提供一个默认构造函数。

9.4 vector对象是如何增长的

为了避免影性能,标准库采用了可以减少容器空间重新分配次数的策略。当不得不获取新的内存空间时,vector和string通常会分配比新的新的空间需求更大的内存空间。容器预留这些空间作为备用,可以用来保存更多的新元素。

容器管理的成员函数:

容器大小管理擦作
c.shrink_to_fit()请将capacity()减少为与size()相同大小
c.capacity()不重新分配内存空间的话,c可以保存多少元素
c.reverse()分配至少能容纳n个元素的内存空间。reverse并不改变容器中元素的数量,它仅影响vector预先分配多大的内存空间。调用reverse永远不减少容器占用的内存空间。

capcacity和size:

区别:

容器的size是指它已经保存的元素的数目;

capcacity则是在不分配新的内存空间的前提下它最多可以保存多少元素。

注意:只有当迫不得已时才可以分配新的内存空间。

9.5 额外的string操作

(1)构造string的其他方法

构造string的其他方法
string s(cp, n)s是cp指向的数组中前n个字符的拷贝
string s(s2, pos2)s是string s2从下标pos2开始的字符的拷贝。
string s (s2, pos2, len2)s是string s2从下标pos2开始len2个字符的拷贝

substr操作:

substr操作返回一个string,它是原始string的一部分或全部的拷贝。

s.substr(pos, n)  返回一个string,包含s中从pos开始的n个字符的拷贝。pos的默认值为0。n的默认值为s.size() - pos, 即拷贝从pos开始的所有字符

(2)改变string的其他方法

assign  替换赋值,总是替换string中的所有内容

insert  插入

append 末尾插入,总是将新字符追加到string末尾

replace 删除再插入

(3)string搜索操作

string搜索操作
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中的字符

(4)compare函数

compare有6个版本,P327

(5)数值转换

P328

tostring

stod

9.6 容器适配器

顺序容器适配器:

stack; queue; priority_queue;

适配器是一种机制,能使某种事物看起来像另外一种事物。

定义一个适配器:

适配器有2个构造函数:

1、默认构造函数创建一个空对象

2、接受一个容器的构造函数

栈适配器:

栈的操作
s.pop()删除栈顶元素,但不返回该元素值
s.push(item)创建一个新元素压入栈顶,该元素通过拷贝或移动item而来,或者由args构造
s.emplace(args)由arg构造
s.top()返回栈顶元素,但不将元素弹出栈

队列适配器:

queue和priority_queue操作
q.pop()返回queue的首元素或priority_queue的最高优先级的元素,但不删除此元素
q.front()                  q.back()返回首元素或尾元素,但不删除此元素。只适用于queue
q.top()返回最高优先级元素,但不删除该元素。只适用于priority_queue
q.push(item) q.empalce(args)在queue末尾或priority_queue中恰当的位置创建一个元素,其值为item,或者由args构造

术语

begin容器操作:返回一个指向容器首元素的迭代器,如果容器为空,则返回尾后迭代器。是否返回const迭代器依赖于容器的类型。

cbegin容器操作:返回一个指向容器尾元素之后的const_iterator。

第十章 泛型算法

P336-P371

标准库并未给每个容器添加大量功能,而是提供了一组算法。这些算法是通用的,可以用于不同类型的容器和不同类型的元素。

10.1 概述

头文件:algorithm、numeric

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

10.2 初识泛型算法

(1)只读算法

accumulate  求和

equal 是否相等

(2)写容器元素的算法

算法不检查写操作

拷贝算法:copy

重排容器元素的算法:sort

::: tip

标准库函数对迭代器而不是容器进行操作。因此,算法不能直接添加或删除元素

:::

10.3 定制操作

标准库允许我们提供自己定义的操作来代替默认运算符。

(1)向算法传递函数

谓词:

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

标准库算法的谓词分为两类:

1、一元谓词:只接受单一参数。

2、二元谓词:接受两个参数。

bool isShorter(const string &s1, const string &s2)
{
    retrun s1.size() < s2.size();
}


sort(words.begin(), words.end(), isShorter);

排序算法:

stable_sort算法维持相等元素的原有顺序。

(2)lambda表达式

lamba:

lambda表达式表示一个可调用的代码单元。一个lambda具有一个返回类型、一个参数列表和一个函数体。

[capture list](parameter list) -> return type {function body}
// capture list 捕获列表,lambda所在函数中定义的局部变量
// 捕获列表只用于局部非static变量,lambda可以直接使用局部static变量和在它所在函数之外声明的名字
// lambda必须使用尾置返回来指定返回类型

(3)lambda捕获和返回

两种:值捕获、引用捕获

::: warnning

当以引用方式捕获一个变量时,必须保证在lambda执行时变量是存在的。

一般的,应该尽量减少捕获的数据量,来避免潜在的问题。

如果可能,避免捕获指针或引用。

:::

隐式捕获

当混合使用隐式捕获和显式捕获时,捕获列表中的第一个元素必须是一个&或=。显式捕获的变量必须使用与隐式捕获不同的方式。

lambda捕获列表 P352

可变lambda:

若希望改变一个被捕获的变量的值,必须在参数列表首加上关键字mutable。

指定lambda返回类型:

当需要为lambda定义返回类型时,必须使用尾置返回类型。

(4)参数绑定

标准库bind函数:

auto newCallable = bind(callable, arg_list);
// 调用newCallable时,newCallable会调用callable,并传递给它arg_list中的参数

10.4 再探迭代器

插入迭代器、流迭代器、反向迭代器、移动迭代器

(1)插入迭代器

back_inserter:创建一个使用push_back的迭代器

front_inserter:创建一个使用push_front的迭代器

inserter:创建一个使用inserter的迭代器

(2)iostream迭代器

istream_iterator 读取输入流

ostream_iterator 向一个输出流写数据

istream_iterator操作:

istream-iterator操作
istream_iterator<T> in(is);in从输入流is读取类型为T的值
istream_iterator<T> end;读取类型为T的值得istream_iterator迭代器,表示尾后位置
in1 == in2              in1 != in2in1和in2必须读取相同类型。如果它们都是尾后迭代器,或绑定到相同的输入,则两者相等
*in返回从流中读取的值
in->mem与(*in).mem含义相同
++in, in++用>>从输入流读取下一个值

ostream_iterator操作:

ostream_iterator操作
ostream_iterator<T> out(os);out将类型为T的值写到输出流os中
ostream_iterator<T> out(os, d);out将类型为T的值写到输出流os中,每个值后面都输出一个d。d指向一个空字符串结尾的字符数组
out = val用<<将val写入到out所绑定的ostream中
*out, ++out, out++

(3)反向迭代器

反向迭代器就是在容器中从尾元素向首元素反向移动的迭代器。

10.5 泛型算法结构

迭代器类别
输入迭代器只读、不写;单遍扫描,只能递增
输出迭代器只写,不读;单遍扫描,只能递增
前向迭代器可读写;多遍扫描,只能递增
双向迭代器可读写,多遍扫描,可递增递减
随机访问迭代器可读写,多遍扫描,支持全部迭代器运算

10.6 特定容器算法

对于list、forward_list,应该优先使用成员函数的算法而不是通用算法。

术语

cref标准库函数:返回一个可拷贝的对象,其中保存了一个指向不可拷贝类型的const对象的引用

第十一章 关联容器

P374-P397

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

类型备注
map关联数组,保存关键字-值对
set值保存关键字的容器
multimap关键字可重复出现的map
multiset关键字可重复出现的set
unordered_map用哈希函数组织的map
unordered_set用哈希函数组织的set
unordered_multimap哈希组织的map;关键字可以重复出现
unordered_multiset哈希组织的set;关键字可以重复出现

11.1 使用关联容器

map是关键词-值对的集合。

为了定义一个map,我们必须指定关键字和值的类型。

// 统计每个单词在输入中出现的次数
map<string, size_t> word_count;
string word;
while (cin >> word)
    ++word_count[word];
for (const auto &w : word_count)
    count << w.first << " cccurs " < w.second 
          << ((w.second > 1) ? " times" : "time") << endl;

set是关键字的简单集合。

为了定义一个set,必须指定其元素类型。

// 统计输入中每个单词出现的次数,并忽略常见单词
map<string, size_t> word_count;
set<string> exclude = {"the", "But"};
string word;
while (cin >> word)
    // 只统计不在exclude中的单词
    if (exclude.find(word) == exclude.end())
        ++word_count[word]; //获取并递增word的计数器

11.2 关联容器概述

(1)定义关联容器

定义map时,必须指明关键字类型又指明值类型;

定义set时,只需指明关键字类型。

(2)pair类型

pair标准库类型定义在头文件utility中。

一个pair保存两个数据成员。当创建一个pair时,必须提供两个类型名。

pair<string, string> anon; // 保存两个string
pair<string, string> author{"James", "Joyce"}; // 也可为每个成员提供初始化器

pair的数据类型是public的,两个成员分别命名为first和second。

pair上的操作,见表,P380

11.3 关联容器操作

关联容器额外的类型别名
key_type此容器类型的关键字类型
mapped_type每个关键字关联的类型,只适用于map
value_type对于set,与key_type相同;对于map,为pair<const key_type, mapped_type>

(1)关联容器迭代器

set的迭代器是const的

set和map的关键字都是const的

遍历关联容器

map和set都支持begin和end操作。使用beigin、end获取迭代器,然后用迭代器来遍历容器。

(2)添加元素

关联容器insert操作
c.insert(v)v是value_type类型的对象;
c.emplace(args)args用来构造一个元素
c.insert(b, e)
c.insert(il)
c.insert(p, v)
c.emplace(p ,args)

(3)删除元素

从关联容器删除元素
c.erase(k)从c中删除每个关键字为k的元素。返回一个size_type值,指出删除的元素的数量
c.erase(p)从c中删除迭代器p指定的元素。p必须指向c中一个真实元素,不能等于c.end()。返回一个指向p之后元素的迭代器,若p指向c中的尾元素,则返回.end()
c.erase(b, e)删除迭代器b和e所表示的范围中的元素。返回e

(4)map的下标操作

map和unorder_map的下标操作
c[k]返回关键字为k的元素;如果k不在c中,添加一个关键字为k的元素,对其进行值初始化
c.at[k]访问关键字为k的元素,带参数检查;若k不在c中,抛出一个out_of_range异常

::: tip

map进行下标操作,会获得mapped_type对象;当解引用时,会得到value_type对象。

:::

(5)访问元素

c.find(k)  // 返回一个迭代器,指向第一个关键字k的元素,如k不在容器中,则返回尾后迭代器
c.count(k)  // 返回关键字等于k的元素的数量。对于不允许重复关键字的容器,返回值永远是0或1
c.lower_bound(k)  // 返回一个迭代器,指向第一个关键字不小于k的元素;不适用于无序容器
c.upper_bound(k)  // 返回一个迭代器,指向第一个关键字大于k的元素;不适用于无序容器
c.equal_bound(k)  // 返回一个迭代器pair,表示关键字等于k的元素的范围。如k不存在,pair的两个成员均等于c.end()

11.4 无序容器

无序容器使用关键字类型的==运算符和一个hash<key_type>类型的对象来组织元素。

无序容器在存储上组织为一组桶,适用一个哈希函数将元素映射到桶。

无序容器管理操作,表格,P395

还可以自定义自己的hash模板 P396

using SD_multiset = unordered_multiset<Sales_data, decltype(hasher)*, decltype(eqOp)*>;
SD_multiset bookStore(42, haser, eqOp);

第十二章 动态内存

P400-P436

12.1 动态指针与智能指针

智能指针用途
shared_ptr提供所有权共享的智能指针:对共享对象来说,当最后一个指向它的shared_ptr被销毁时会被释放。
unique_ptr提供独享所有权的智能指针:当unique_ptr被销毁的时,它指向的独享被释放。unique_ptr不能直接拷贝或赋值。
weak_ptr一种智能指针,指向由shared_ptr管理的对象。在确定是否应释放对象视,shared_ptr并不把weak_ptr统计在内。

(1)shared_ptr类

shared_ptr<string> p1;

make_shared函数:

make_shared在动态内存中分配一个对象并初始化它,返回此对象的shared_ptr。

share_ptr<int> p3 = make_shared<int>(42);

shared_ptr的拷贝和赋值:

每个shared_ptr都有一个关联的计数器,称为引用计数。一旦一个shared_ptr的引用计数变为0,就会自动释放自己所管理的对象。

(2)直接管理内存

运算符new分配分配内存,delete释放new分配的内存。

使用new动态分配和初始化对象:

// 默认情况下,动态分配的对象是默认初始化的
int *pi = new int; // pi指向一个动态分配的、未初始化的无名对象


// 直接初始化方式
int *pi = new int(1024); // pi指向的对象的值为1024


// 对动态分配的对象进行值初始化,只需在类型名之后加上一对空括号
int

以上是关于两万字总结《C++ Primer》要点的主要内容,如果未能解决你的问题,请参考以下文章

超硬核知识:两万字总结《C++ Primer》要点!

两万字长文,史上最全 C++ 年度总结!

两万字长文,史上最全 C++ 年度总结!

两万字长文,史上最全 C++ 年度总结!

[数据结构]——线性表总结(c语言代码实现)爆肝两万字!

[数据结构]——线性表总结(c语言代码实现)爆肝两万字!