菜单

Dau
Dau
发布于 2025-01-18 / 29 阅读
0
0

Stanford CS106L Notes

StringStream

stream作为数据类型和最终输出的中间单位,每个obj有一个buffer

stringstream分为ostringstream和istringstream,使用流运算符<<,>>来进行操作,并用两个指针(get和put)来管理输入输出流,会自动进行数据类型转换。

在istringstream里,要输入到一个变量里,则从当前get指针位置开始读取对应变量类型(自动进行类型转换),直到读取的字符为空(空格,换行,tab)或不能够转换成对应数据类型。

例如:A.str()="12.3"; 时,若要输入到一个整型变量B里,则最终B的值是12,而A的get指针停留在 ' . ' 上。

流对象执行输入输出操作时会维护一组状态标志,包括eofbit,failbit,badbit,例如,当将缓冲区字符串为”hello“的流输入到整型变量时,会出现输入失败的情况,这时流被标记为fail,只有进行clear操作后才可以继续操作。

ostringstream和istringstream的操作符与功能可类比cin和cout

含有stream operator(<< or >>)的表达式返回流对象本身,并且可以当作bool值(若statebit不是fail或者eof,则返回true,反之则为false)。

cout流有一个缓冲区,所有通过<<输入的数据对象需要等到flush的时候才被打印在屏幕上,endl也算是一个flush,会影响运行速度,但在有些时候不进行flush操作会导致错误或者不便。

cin每次读取会读取一行(直到回车),而其作为输入流在程序里和流的行为一样(按照空白符号分段读取),有可能会引起问题。另外,一个cin结束后指针在空白字符之前,而在下一个cin语句的时候,指针才会跳过这些空白字符

解决:getline(cin,stringname);替换cin>>表达式,直接读到换行符(并且consume换行符,获得的字符串中不包括换行符)

但>>和getline混用可能会出现问题,因为>>读取后指针停在空白符之前,而若此空白符是\n,那么下一次getline就只会返回一个空字符串。

Type

type aliases:类型别名

使用typedef或者using语句可以简化复杂的类型名

 

auto关键字:自动推断

需要初始化(以便推断类型)

不加&则去引用。若需要引用类型就要显式声明auto&

去掉const和volatile修饰符。若需要const就要显式声明const auto

用于初始化变量的数据类型如果含有指针,则保留constness,例如:

const int* px = 100;

auto py = px; // py 的类型是 const int* (const 被保留)

又例如:

auto d = "world!"; // d 的类型是 const char*

由于“world”字面量默认被推导为c风格字符串(char*),含有指针,故保留constness,而若有如下操作:

auto e = string("world!");

则类型推断为string,不含有指针,故不保留constness

 

引用&

 

使用pair和tuple将多个值打包,用auto可以简化操作

使用struct可以为变量取名,加以区分

使用结构绑定可以简化从pair或tuple或struct取值的操作

Containers&Iterator

Initialization

Uniform Initialization:使用{}初始化列表初始化大部分的对象,从而避免了初始化方法过多的麻烦,如:

Vector<int> vec{12,12,12,45,46,23};

本质上是调用了vector类的带有初始化列表参数的构造函数

 

Sequence Containers

Vector(在前面插入速度慢,但访问速度比deque快)

Deque(能够实现前后都比较快的插入速度,但访问速度比vector慢)

else:

list:链表

Forward_list:单向链表

array:定长数组

 

Container Adaptors

stack:基于vector/deque实现

queue:基于deque实现

 

Associative Containers

Set:存储单个值,自动排序,快速查找。

Map:存储不同数据类型的键值对映射,相当于把sequence里的index变成了key(但存储方式和sequence不同),元素按照key有特定顺序,可以自定义<运算符来定义排序。能够进行快速查找(二叉树结构)。

[key]方法:若没有key对应的值,则新建键值对

.get(key)方法:若没有key对应的值,则throw an error

判断key是否存在:

.count(key)方法,返回key在map里出现的次数(只可能是0或者1)

.find(key)方法,返回key第一次出现的迭代器,若key不存在则返回.end()迭代器

multimap:允许一个键对应多个元素,但不再有[key]运算符,.find(key)返回key对应的第一个元素,.range(key)返回一个pair<begin_iterator,end_iterator>,表示对应key的元素范围,使用iterator从begin到end进行遍历,可获取key对应的全部元素

map和multimap存储的都是pair,意味着iterator的解引用结果是一个pair

set和map在使用for each遍历的时候是只读的,因为修改元素会破坏容器结构,导致for循环不安全

 

sequence和associative对比:associative查找超快,sequence可以随机访问

 

Iterator

可以对所有Container进行遍历,以int型的set为例:

Set<int>::iterator it=mySet.begin();

容器可调用相应函数返回iterator值

.begin()

.end()

对容器使用操作符

++it:将iterator移动到下一个元素

*it:对iterator进行解引用(it不一定是指针,但相似)

Types(低级到高级)

Input/output

Forward

Bidirectional

Random access

根据层级不同,可使用的操作不一样,如set的iterator不支持随机访问(+3),只能进行递增(++),而vector就可以,具体要考虑到容器的底层数据存储方式

Template

用于泛型编程,使一个函数能接收不同类型的参数而无需重载(以及更多功能)

语法:

template <typename T,typename U,…… >    // 其中,template是声明模板的标识,<>内是模板参数列表,typename代表T是一个类型名变量

函数声明定义语句(用T代表要使用泛型的类型)

调用的时候和普通情况一样,也可以用显式实例化(explicit instantiation),即在调用的函数名后面用<类型名> 传入模板参数

隐式实例化时,字符串字面量会被认为是char*而非string,所以可能会出问题

 

声明模板类时,一般要求把声明和实现都放在.h文件里,如果分离声明和实现会报LNK错误

如果需要分离,则需要用到显式实例化,即在.cpp文件里先通过template class className<typename>来提前对typename类型的类的成员函数进行实例化,并且只有在.cpp里实例化过的类型才能使用模板函数

 

Implicit interface

使用模板并不是传入任何类型的参数都能工作,模板函数里对泛型变量的操作对传入参数的类型作了限制

concept(explicit interface)

c++20:显式声明参数的限制,不用看函数定义就能知晓限制

 

模板函数可以传入参数函数(谓词函数)

函数作为参数传入模板函数时,实例化出一个有函数指针类型参数的函数原型

谓词函数是对返回布尔值的函数的叫法,仅仅为了方便,和平常函数并没有分别,但由于模板化的需要,这类函数使用得比较频繁

使用stl库时所需要的谓词函数,往往是一元或者二元的(倘若库函数需要一元谓词函数,但又有再传入其他参数的需求,就可以考虑lambda的捕获或者仿函数)

谓词可以是:函数,函数对象(仿函数),lambda表达式(实际也是仿函数),函数指针

Lambda

谓词函数可以用lambda表达式替代,lambda表达式实际上是仿函数,是一个重载了()运算符的类

lambda表达式语法:

[capture](parameter)->return type{

Statements;

}

  • capture: 捕获列表,用于指定哪些外部变量可以在 lambda 表达式中使用。捕获方式有值捕获([x])、引用捕获([&x])以及捕获所有变量([=]、[&])等。

  • parameters: 函数参数列表,和普通函数类似。(这里的参数列表可以使用auto)

  • return_type: 返回类型,可以省略,编译器会自动推导。

  • 可以使用auto objectname=lambda;来把函数对象存起来

Algorithm

sort:按照条件排序

Stable partition:按照条件将容器元素分组,分别在前面和后面,但同组元素相对顺序不变

Copy_if:按照条件复制,需要传入一个output iterator,并且output it不能自动扩容,如需要扩容,则传入back_inserter(容器名)

Back_inserter是一个iterator adaptor,用于生成可扩容的迭代器

相似的,还有ostream_iterator,可以生成流的迭代器

Remove_if:按照条件移除,但是注意:STL函数并不是容器的成员函数,所以并不能修改容器的大小,而只是把移除的元素都放在了容器的最后面,并返回指向最前面的要移除元素的指针,而容器end指针不变

并且已经使用remove指定移除的元素,虽然没有被完全移除,但并不可用,只是垃圾,需要手动清理

完全移除元素的组合拳:↑

find(find_if):

 

Const

1.限制函数参数,一般和&一起,防止传入的参数被改变,如果函数内部试图改变此参数,编译器会报错。

同时也限制了函数内调用此参数的其他函数

2.使用const_cast转换constness,不常用

3.和class协同,const member function

声明方法:在函数原型声明后加const

类的const对象只能调用const member function

非const对象能调用所有类型的成员函数

4.const pointer

Int* const p;

指针本身为常量

Const int* p;

指针指向的对象为常量

Const int* const p;

指针本身和指向的对象皆为常量

5.const iterator

Const vector<int>::iterator it;

迭代器本身为常量

Vector<int>::const_iterator it;

迭代器指向的对象为常量

6.auto不自动包含const和&,故需要显式声明const auto &p=something

Operators

1.当使用运算符的时候,c++实际上在调用函数

这里运算符是作为成员函数被调用,实际上,也可以使用非成员函数形式(有些运算符只有成员函数形式)

 

2.重载

syntax:(类外)returnType className::operator specificOperator(parameter list){func body;}

通常,算术运算符返回的是运算结果的引用,标准格式如下

this:一个指向调用当前函数对象的指针,类型为className* const,*this返回对象的引用

作为成员函数的二元运算符重载只需要一个参数,因为另一个参数是对象本身

非成员函数的二元运算符重载则需要两个参数

对于非成员函数的运算符重载,如需要访问privata成员变量,可在类内将该函数声明为friend

 

 

SpecialMemberFunction

Special Functions

Constructor

当声明一个类的成员变量为常量时,不允许在其构造函数体内对变量进行初始化,这时候需要用到初始化列表(适用于所有只初始化一次,而不再后续改变的情况)(非常量的成员函数也可以使用这个语法)

并且初始化列表里的变量名自动指代本对象里的成员变量,而无需使用this指针

若一个类的对象被声明为常量,对其非常量成员变量的初始化,既可以在初始化列表进行,也可以在构造函数体内进行

Copy

Copy constructor

创建新对象时,若使用另一个已有的对象来初始化此对象,则调用此函数

自己实现的方法为重载一个参数为本类型的构造函数

要分清楚简单拷贝和深层拷贝

考虑使用no

Copy assignment

对于一个已有的对象,用另一个已有对象赋值给它,调用此函数

实现方法为重载 = 运算符

若一个类内存在不能复制的元素,如文件流,可将copy constructor和copy assignment显式声明为delete

当涉及到手动分配的内存时,就考虑实现上面两者和destructor

Rule of three(five):copy constructor,copy assignment,destructor三者同时实现或者     不实现

 

Move Semantics

函数间传输对象的时候,往往会产生不必要的copy,如参数传入,返回结果,可以优化

aside:vector的emplace_back方法,相比于push_back,本方法直接传入构造一个对象的参数,而无需传入一个已经构造好的对象,避免不必要的copy

左值/右值(L/R value):

左值为有地址的值,一般有名字,解引用的指针也是左值

右值为无地址的临时量,如字面量,不返回引用的函数返回值

引用:

&表示左值引用,比较常用;&&表示右值引用,使得被引用的右值生命周期增长

常量类型的左值引用也可以绑定到右值上

原型:

即设计参数为右值引用版本的copy constructor和copy assignment,由于常量类型的左值引用也可以绑定到右值上,所以先前的copy函数也能用

实现上,相比于原先的深度拷贝,对于右值引用,可以直接窃取其数据,而无需创建新的数据

对于以右值引用传入的参数,其本身却是左值,因此进行拷贝时可能不会按照预期进行,此时可以使用std::move()函数,该函数返回传入参数的右值版本

Rule of five:

copy constructor,copy assignment,destructor,move constructor,move assignment五者同时实现或者不实现

 

 

Namespace&Inheritance

Inheritance from the first tutorial

Inheritance:类声明第一行后面加上

:public 父类名

父子类构造函数调用顺序和重载问题

不论有无输入参数,先调用父类默认(即不带输入参数的)构造函数,在调用本类对应输入参数的重载构造函数

如果使用以下语法(假设创建Dog实例时传入了下图三个参数)

则会调用父类对应参数的重载构造函数,而不调用无参数的构造函数

如果在Dog的构造函数体内调用了Animal构造函数(而不是使用冒号初始化列表),则先调用Animal的默认构造函数,再调用Dog的重载构造函数,再调用Animal的重载构造函数(并且最后调用的这个函数并不会影响到实际对象,而是创建了一个虚拟对象来操作),可以说是错误用法。

 

Private:仅限类内

Protected:仅限类内和派生类

Public:都行

关键字继承:(不写关键字则类默认private,结构体默认public)

例:class Dog : protected Animal {} ;

Animal类中所有public变量都变成protected,其余不变

 

Virtual:

非静态成员函数前加上virtual关键字

派生类即可重写(override)此函数,方法为(例)void Speak(){}(不用加virtual),并且此函数在派生类中依旧可以被重写(隐式标注了virtual)

为了可读性,可以在重写函数前加上virtual(代表此函数可以被重写),也可以在重写函数后加上override(代表此函数是基类一个函数的重写)

可以使用完全限定符来调用基类的被重写的原函数

 

Polymorphism

一个类实例的指针可以指向其所有子类的实例,并且调用继承函数时会调用相应的重写函数

 

Multi-Inheritance

使用“,”分隔开父类即可,每个父类前都要加关键字

对于菱形继承:

使用virtual关键字,确保只存在一个基类实例

 

Namespace

使用namespace name{}定义自己的命名空间

好的习惯:

不要总是using namespace std,在大项目里应用using std::something

在header里也尽量别用using

 

inheritance

Interface:另一种理解继承的方式,即

父类不提供具体实现,而是提供接口,因此标记为virtual

子类继承父类,继承了接口并实现接口,这样就有了统一的方法

Virtual void functionName()=0;(纯虚函数)

在接口类声明函数时赋值为0,可强制子类实现该函数

当一个类包含纯虚函数时,该类(抽象类)不能实例化

当子类新定义了一个名字和父类相同的变量时,父类里的那个变量会被隐藏

若一个类想要被继承,最好将其析构函数声明为virtual

若基类析构函数没有被声明为virtual,则在使用多态时(一个基类的指针指向派生类),派生类的析构函数不会被正确调用,delete只会调用基类的析构函数,导致内存泄漏

多态性:template vs. derived classes

template是静态多态,在编译时就已经决定好了;derived是动态多态,直到运行时才知道对象类型

template多种对象实现的功能类似;derived多种对象实现的功能可以完全不同

 

 

RAII&SmartPointer

程序在抛出异常的时候可能不会正确释放内存,导致内存泄漏

如:new,文件流,lock

 

RAII

鉴于析构函数在每次退出作用域的时候都会被调用,因此可以直接将释放内存的操作写在析构函数里

Constructor builds, destructor releases

例如,ifstream就内置了RAII,使其在作用域末尾无需手动调用close方法

对于lock,在作用域开头定义一个lock_guard类,用lock将其初始化;而在lock_guard类的构造函数中lock被锁上,在lock_guard的析构函数中lock被解锁

Smart pointers

原理如上面lock_guard,标准库提供了用于管理指针内存的类

Unique_ptr

不允许复制操作

初始化:unique_ptr<node> p=make_unique<node>;

Shared_ptr

允许复制(多个指针指向同一个数据地址),有一个计数器,内存在计数器为零的时候被释放

初始化:shared_ptr<node> p=make_shared<node>;

Weak_ptr

 

使用try,catch语句来捕获异常

 

MultiThreading

概述

使用atomic类型,尤其是在注重顺序的情况下,防止数据竞争data race

Condition variable进行线程间通信

 

使用thread类名声明线程(就像其他类一样)

thread没有copy函数,不能被拷贝

使用join()方法来等待线程结束


评论