Lecture 1
命令提示符
- 目录结构
D:\example\ (Win)
/home/username/example/ (Linux)
/Users/username/example/ (MAC)
- 显示当前目录
cd (Win)
pwd (Linux/MAC)
- 在当前目录下新建OOP2021目录
mkdir OOP2021 (Win/Linux/MAC)
- 在当前目录下新建a.cpp文件
type nul>a.cpp (Win)
touch a.cpp (Linux/MAC)
- 查看当前目录下的文件
dir (Win)
ls (Linux/MAC)
- 进入上一层目录
cd .. (Win/Linux/MAC)
- 进入OOP2021目录
cd OOP2021 (Win/Linux/MAC)
- 删除a.cpp文件
del a.cpp (Win)
rm a.cpp (Linux/MAC)
- 删除OOP2021目录以及目录下的所有文件
rmdir /s OOP2021 (Win)
rm –r OOP2021 (Linux/MAC)
Lecture 2
声明与定义
- 函数声明时可省略变量名,有且只能有一次实现(定义),否则会链接错误
- 若仅声明变量,需要extern关键字
- 若头文件中定义全局变量且多个cpp文件包含此头文件,在链接时会因重复定义而发生错误
- 带参数的宏定义:
#define <宏名>(<参数表>) <字符串>
#define sqr(x) ( (x) * (x) )
GDB调试工具
g++ -g a.cpp –o a.out编译程序
-g 在可执行程序中包含标准调试信息
gdb a.out 调试a.out程序
run 运行程序
break + 行号 设置断点
break 10 if (k==2) 可根据具体运行条件断点
delete break 1 删除1号断点
watch x 当x的值发生变化时暂停
continue 跳至下一个断点
step 单步执行(进入)
next 单步执行(不进入)
print x 输出变量/表达式x
GDB中输入 p x=1,程序中x的值会被手动修改为1
display x 持续监测变量/表达式x
list 列出程序源代码
quit 退出
回车 重复上一条指令
makefile较详细总结:
https://www.ruanyifeng.com/blog/2015/02/make.html
Lecture 3
函数重载
- 多个同名的函数实现之间,必须保证至少有一个函数参数的类型有区别。返回值、参数名称等不能作为区分标识。
- 有缺省值的函数参数,必须是最后一个参数。如果有多个带缺省值的函数参数,则这些函数参数都只能在没有缺省值的参数后面出现。
Lecture 4
- 构造函数的初始化列表:
初始化列表的成员是按照声明的顺序初始化的,而不是按照出现在初始化列表中的顺序。
在构造函数的初始化列表中,还可以调用其他构造函数,称为“委派构造函数”。 - 析构函数:
1.对于成员变量含其他类对象的类析构函数,先执行自己的析构函数,再调用成员变量的析构函数。
2.注意隐式定义的析构函数不会delete指针成员,因此可能造成内存泄漏。 - 运算符重载:
数组运算符重载格式:返回类型 operator[] (参数);
如果返回类型是引用,则数组运算符调用可以出现在等号左边,接受赋值,
即Obj[index] = value;
如果返回类型不是引用,则只能出现在等号右边,
即Var = Obj[index];
Lecture 5
友元
- 友元函数:
1.可以声明别的类的成员函数,为当前类的友元。
其中,构造函数、析构函数也可以是友元。
2.友元的声明与当前所在域是否为private或public无关。 - 友元类:
1.非对称关系:
类A中声明B是A的友元类,则B可以访问A的私有成员,但A不能访问B的私有成员。
2.友元不传递:
(朋友的朋友不是你的朋友)
3.友元不继承:
(朋友的孩子不是你的朋友)
4.友元声明不能定义新的class,例如:class X { friend class Y {}; //error }
静态成员
- 静态数据成员:使用static修饰的数据成员,是隶属于类的,称为类的静态数据成员,也称“类变量”。
静态数据成员被该类的所有对象共享(即所有对象中的这个数据域处在同一内存位置)
类的静态成员(数据、函数)既可以通过对象来访问,也可以通过类名来访问,如ClassName::static_var
或者a.static_var
(a为ClassName类的对象)
类的静态数据成员要在实现文件中赋初值,格式为:
Type ClassName::static_var = Value;
和全局变量一样,类的静态数据成员在程序开始前初始化。 - 静态成员函数:静态成员函数:在返回值前面添加static修饰的成员函数,称为类的静态成员函数。
和静态数据成员类似,类的静态成员函数既可以通过对象来访问,也可以通过类名来访问,如ClassName::static_function
或者a.static_function
(a为ClassName类的对象)
静态成员函数不能访问非静态成员
静态成员函数属于整个类,在类实例化对象之前已经分配了内存空间。
类的非静态成员必须在类实例化对象后才分配内存空间。
如果使用静态成员函数访问非静态成员,相当于没有定义一个变量却要使用它。
常量成员
- 常量数据成员:使用const修饰的数据成员,称为类的常量数据成员,在对象的整个生命周期里不可更改
常量数据成员可以在:
1.构造函数的初始化列表中被初始化
2.就地初始化
3.不允许在构造函数的函数体中通过赋值来设置 - 常量成员函数:
成员函数也能用const来修饰,称为常量成员函数。
常量成员函数的访问权限:实现语句不能修改类的数据成员,即不能改变对象状态(内容)
ReturnType Func(…) const {…}
注意区别:const ReturnType Func(…) {…}
若对象被定义为常量(const ClassName a;)
,则它只能调用以const修饰的成员函数 -
常量对象:对象中的“数据”不能变。
Lecture 6
拷贝构造/移动构造函数;拷贝赋值/移动赋值运算符
- 禁用返回值优化:
g++ test.cpp --std=c++11 -fno-elide-constructors –o test
- 用一个已定义的类对象初始化另一个新的类对象:拷贝构造/移动构造函数
两个已定义的类对象之间相互赋值:拷贝赋值/移动赋值运算符
自动类型转换
- 在源类中定义“目标类型转换运算符”
class Src { //源类Source
public:
Src() { cout << "Src::Src()" << endl; }
operator Dst() const {
cout << "Src::operator Dst() called" << endl;
return Dst();
}
};
- 在目标类中定义“源类对象作参数的构造函数”
class Dst { public: Dst() { cout << "Dst::Dst()" << endl; } Dst(const Src& s) { cout << "Dst::Dst(const Src&)" << endl; } };
- 禁止自动类型转换:
如果用explicit
修饰类型转换运算符或类型转换构造函数,则相应的类型转换必须显式地进行,即:
explicit operator Dst() const;
或explicit Dst(const Src& s);
强制类型转换
-
const_cast
,去除类型的const或volatile属性。
static_cast
,类似于C风格的强制转换。无条件转换,静态类型转换。
dynamic_cast
,动态类型转换,如派生类和基类之间的多态类型转换。
reinterpret_cast
,仅仅重新解释类型,但没有进行二进制的转换。
Lecture 7
组合
- 子对象构造时若需要参数,则应在当前类的构造函数的初始化列表中进行。若使用默认构造函数来构造子对象,则不用做任何处理。
- 构造与析构过程:
1.先完成子对象构造,再完成当前对象构造。
2.子对象构造的次序仅由在类中声明的次序所决定。
3.析构函数的次序与构造函数相反。
继承
- 不能被继承的:
构造函数
:创建派生类对象时,必须调用派生类的构造函数,派生类构造函数调用基类的构造函数,以创建派生对象的基类部分。C++11新增了继承构造函数的机制(使用using),但默认不继承。
析构函数
:释放对象时,先调用派生类析构函数,再调用基类析构函数。
赋值运算符
:编译器不会继承基类的赋值运算符(参数为基类)
但会自动合成隐式定义的赋值运算符(参数为派生类),其功能为调用基类的赋值运算符。
友元函数
:不是类成员 - 构造与析构过程:
基类中的数据成员,通过继承成为派生类对象的一部分,需要在构造派生类对象的过程中调用基类构造函数来正确初始化。
若没有显式调用,则编译器会自动调用基类的默认构造函数。
若想要显式调用,则只能在派生类构造函数的初始化成员列表中进行,既可以调用基类中不带参数的默认构造函数,也可以调用合适的带参数的其他构造函数。
先执行基类的构造函数来初始化继承来的数据,再执行派生类的构造函数。
对象析构时,先执行派生类析构函数,再执行由编译器自动调用的基类的析构函数。 - 用using继承基类构造函数的细节:
如果基类的某个构造函数被声明为私有成员函数,则不能在派生类中声明继承该构造函数。
如果派生类使用了继承构造函数,编译器就不会再为派生类生成隐式定义的默认构造函数。 - 成员访问权限:
1.基类中的私有成员:
不允许在派生类成员函数中访问,也不允许派生类的对象访问它们。
真正体现“基类私有”,对派生类也不开放其权限!
2.基类中的公有成员:
允许在派生类成员函数中被访问
若是使用public继承方式,则成为派生类公有成员,可以被派生类的对象访问;若是使用private/protected继承方式,则成为派生类私有/保护成员,不能被派生类的对象访问。若想让某成员能被派生类的对象访问,可在派生类public部分用关键字using声明它的名字。
3.基类中的保护成员:
保护成员允许在派生类成员函数中被访问,但不能被外部函数访问。 -
不允许私有继承的向上转换。
重写隐藏
- 重写隐藏发生时,基类中该成员函数的其他重载函数都将被屏蔽掉,不能提供给派生类对象使用。
- 可以在派生类中通过using 类名::成员函数名; 在派生类中“恢复”指定的基类成员函数(即去掉屏蔽),使之重新可用。
Lecture 8
向上类型转换
- 派生类对象/引用/指针转换成基类对象/引用/指针,称为向上类型转换。只对
public
继承有效,在继承图上是上升的;对private
、protected
继承无效。 - 向上类型转换(派生类到基类)可以由编译器自动完成,是一种隐式类型转换。
- 凡是接受基类对象/引用/指针的地方(如函数参数),都可以使用派生类对象/引用/指针,编译器会自动将派生类对象转换为基类对象以便使用。
虚函数
- 对于被派生类重新定义的成员函数,若它在基类中被声明为虚函数,则通过基类指针或引用调用该成员函数时,编译器将根据所指(或引用)对象的实际类型决定是调用基类中的函数,还是调用派生类重写的函数。
- 若某成员函数在基类中声明为虚函数,当派生类重写覆盖它时(同名,同参数函数) ,无论是否声明为虚函数,该成员函数都仍然是虚函数。
- 虚机制在构造函数及析构函数中均不工作。即:在其中调用虚函数时,均只调用本地版本。
- 重要原则:总是将基类的析构函数设置为虚析构函数。
-重载、重写覆盖与重写隐藏
1.重载(overload)
:
函数名必须相同,函数参数必须不同,作用域相同(同一个类,或同为全局函数),返回值可以相同或不同。
2.重写覆盖(override)
:
派生类重新定义基类中的虚函数,函数名必须相同,函数参数必须相同,返回值一般情况应相同。
派生类的虚函数表中原基类的虚函数指针会被派生类中重新定义的虚函数指针覆盖掉。重写覆盖同样会隐藏基类的同名函数。
3.重写隐藏(redefining)
:
派生类重新定义基类中的函数,函数名相同,但是参数不同或者基类的函数不是虚函数。(参数相同+虚函数->不是重写隐藏)
重写隐藏中虚函数表不会发生覆盖。 - 不想让使用者继承?
final
关键字!
在虚函数声明或定义中使用时,final
确保函数为虚且不可被派生类重写。可在继承关系链的“中途”进行设定,禁止后续派生类对指定虚函数重写。
在类定义中使用时,final
指定此类不可被继承。class Base{ virtual void foo(){}; }; class A: public Base { void foo() final {}; /// 重写覆盖,且是最终覆盖 void bar() final {}; /// bar 非虚函数,编译错误 }; class B final : public A{ void foo() override {}; /// A::foo 已是最终覆盖,编译错误 }; class C : public B{ /// B 不能被继承,编译错误 };
Lecture 9
纯虚函数与抽象类
- 纯虚析构函数仍然需要函数体,但派生类不必实现纯虚析构函数。
向下类型转换
-
dynamic_cast
,是一种安全的向下类型转换。
使用dynamic_cast
的对象必须有虚函数,因为它使用了存储在虚函数表中的信息判断实际的类型。
使用方法:
obj_p
,obj_r
分别是T1
类型的指针和引用T2* pObj = dynamic_cast<T2*>(obj_p); //转换为T2指针,运行时失败返回nullptr T2& refObj = dynamic_cast<T2&>(obj_r); //转换为T2引用,运行时失败抛出bad_cast异常
在向下转换中,T1
必须是多态类型(声明或继承了至少一个虚函数的类),否则无法通过编译。
- 如果我们知道正在处理的是哪些类型,可以使用
static_cast
来避免这种开销。
static_cast
在编译时静态浏览类层次,只检查继承关系。没有继承关系的类之间,必须具有转换途径才能进行转换(要么自定义,要么是语言语法支持),否则不过编译。运行时无法确认是否正确转换。
static_cast
使用方法:
obj_p
,obj_r
分别是T1
类型的指针和引用
T2* pObj = static_cast<T2*>(obj_p);
//转换为T2指针
T2& refObj = static_cast<T2&>(obj_r);
//转换为T2引用
不安全:不保证指向目标是T2
对象,可能导致非法内存访问。
-
dynamic_cast
也能对指针或引用进行向上类型转换。(较少使用,因为向上转换支持隐式转换)
static_cast
也能对不同对象类型进行转换
模板与多态
- 模板使用泛型标记,使用同一段代码,来关联不同但相似的特定行为,最后可以获得不同的结果。模板也是多态的一种体现。
但模板的关联是在编译期处理,称为静多态。
1.往往和函数重载同时使用
2.高效,省去函数调用
3.编译后代码增多
基于继承和虚函数的多态在运行期处理,称为动多态。
1.运行时,灵活方便
2.侵入式,必须继承
3.存在函数调用 - 类模板的“模板参数”
类型参数:使用typename
或class
标记
非类型参数:整数,枚举,指针(指向对象或函数),引用(引用对象或引用函数)。无符号整数(unsigned)比较常用。如:
template<typename T, unsigned size>
class array {
T elems[size];
};
array<char, 10> array0;
- 模板类的成员函数,也可有额外的模板参数:
template<typename T0> class A {
T0 value;
public:
template<typename T1> void set(T1 const& v){
value = T0(v); /// 将T1转换为T0储存
} /// 在类内定义
template<typename T1> T1 get();
};
template<typename T0> template<typename T1>
T1 A<T0>::get(){ return T1(value);}
/// 类外定义, 将T0转换为T1返回
注意不能写成:
template<typename T0, typename T1>
T1 A<T0>::get(){ return T1(value);} � /// 错误,与多个参数的模板混淆
template<typename T0> template<typename T1>
T1 A<T0>::get(){ return T1(value);} /// 正确
Lecture 11
STL
-
STL(Standard Template Library)
是基于模板编写的高效的C++软件库。其中包含4个组件,分别为算法、容器、函数、迭代器
。 - STL的命名空间是
std
一般使用std::name
来使用STL的函数或对象。
也可以使用using namespace std
来引入STL的命名空间(不推荐在大型工程中使用,容易污染命名空间)。 - tie函数—返回左值引用的元组
std::string x; double y; int z;
std::tie(x, y, z) = std::make_tuple(“abc”, 7.8, 123);
//等价于 x = "abc"; y = 7.8; z = 123
- 通过std::get函数获取tuple中的数据。
auto t = std::make_tuple(“abc”, 7.8, 123, ‘3’);
auto v0 = std::get<0>(t);
auto v1 = std::get<1>(t);
其下标需要在编译时确定:不能设定运行时可变的长度,不能当做数组!
vector
:
允许直接以下标访问。(高速)
创建:std:vector<int> x;
当前数组长度:x.size();
清空:x.clear();
在末尾添加/删除:(高速)
x.push_back(1);
x.pop_back();
(使用迭代器)在中间添加/删除:(低速)
x.insert(x.begin()+1, 5);
x.erase(x.begin()+1);
迭代器(并非所有容器的迭代器均可):
下一个元素:++iter
上一个元素:--iter
下n个元素:iter += n
上n个元素:iter -= n
访问元素值——解引用运算符 :
*iter = 5;
解引用运算符返回的是左值引用。
迭代器移动:与整数作加法
iter += 5;
元素位置差:迭代器相减
int dist = iter1 – iter2;
其本质都是重定义运算符。list
:
插入前端:
l.push_front(1);
插入末端:
l.push_back(2);
查询:
std::find(l.begin(), l.end(), 2); //返回迭代器
插入指定位置:
l.insert(it, 4); //it为迭代器
不支持下标等随机访问
支持在任意位置高速插入/删除数据
其访问主要依赖迭代器
插入和删除操作不会导致迭代器失效(除指向被删除的元素的迭代器外)set
:(不重复元素构成的无序集合)
插入(不允许出现重复元素):
s.insert(val);
查询值为val的元素:
s.find(val); //返回迭代器
删除:
s.erase(s.find(val)); //导致迭代器失效
统计:
s.count(val); //val的个数,总是0或1
map
:
其值类型为pair<Key, T>
。
map中的元素key必须互不相同。
可以通过下标访问(即使key
不是整数)。下标访问时如果元素不存在,则创建对应元素。
也可使用insert函数进行插入。
#include <string>
#include <map>
int main() {
std::map<std::string, int> s;
s["Monday"] = 1;
s.insert(std::make_pair(std::string("Tuesday"), 2));
return 0;
}
查询键为key的元素:
(注意,迭代器指向一个pair,而不是指向key对应的T!!!)
s.find(key); // 返回迭代器
统计键为key的元素个数:
s.count(key); // 返回0或1
删除:
s.erase(s.find(key)); //导致被删元素的迭代器失效
STL部分基本容器小结:
序列容器:vector
、list
关联容器:set
、map
序列容器与关联容器的区别:
序列容器中的元素有顺序,可以按顺序访问。
关联容器中的元素无顺序,可以按数值(大小)访问。
vector
中插入删除操作会使操作位置之后全部的迭代器失效。
其他容器中只有被删除元素的迭代器失效。
模板特化:参见课件L11(p64-p75)
Lecture 12
string类
- 允许简洁的拼接操作
string fullname = firstname + " " + lastname;
- 构造方式
string s0("Initial string");
//从c风格字符串构造
string s1;
//默认空字符串
string s2(s0, 8, 3);
//截取:“str”,index从8开始,长度为3
string s3(“Another character sequence”, 12);
//截取:“Another char”
string s4(10, 'x');
//复制字符:xxxxxxxxxx
string s5(s0.begin(), s0.begin()+7);
//复制截取: Initial - 转换为c风格字符串:
str.c_str()
//注意返回值为常量字符指针(const char*),不能修改 - 和vector类似的使用方法:
访问/修改元素:cout << str[1]; str[1]='a';
查询长度:str.size()
清空:str.clear()
查询是否为空:str.empty()
迭代访问:for(char c : str)
向尾部增加:
str.push_back('a');
str.append(s2);
不同之处:
查询长度也可以使用str.length()
,与size()
返回值相同
向尾部增加也可以使用str += 'a'
或者str += s2
- string常用转换函数:
- 数值类型字符串化:
to_string(1) //"1"
to_string(3.14) //"3.14"
to_string(3.1415926) //"3.141593" 注意精度损失
to_string(1+2+3) //"6"
- 字符串转数值类型:
int a = stoi("2001"); //a=2001
std::string::size_type sz; //代表长度的类型 无符号整数
int b = stoi("50 cats", &sz); //b=50 sz=2 代表读入长度
int c = stoi("40c3", nullptr, 16); //c=0x40c3 十六进制
int d = stoi("0x7f", nullptr, 0); //d=0x7f 自动检查进制
double e = stod("34.5"); //e=34.5
格式化输出 – #include <iomanip>
cout << fixed << 2018.0 << " " << 0.0001 << endl;
//浮点数 -> 2018.000000 0.000100
cout << scientific << 2018.0 << " " << 0.0001 << endl;
//科学计数法 -> 2.018000e+03 1.000000e-04
cout << defaultfloat; //还原默认输出格式
cout << setprecision(2) << 3.1415926 << endl;
//输出精度设置为2 -> 3.2
cout << oct << 12 << " " << hex << 12 << endl;
//八进制输出 -> 14 十六进制输出 -> c
cout << dec; //还原十进制
cout << setw(3) << setfill('*') << 5 << endl;
//设置对齐长度为3,对齐字符为* -> **5
- 各种控制输入输出格式的流操纵算子均是输入/输出流的辅助类,实例对象为辅助类的类对象,而不是流的对象!
- endl是一个函数(也属于流操纵算子):
ostream& endl(ostream& os);
(声明)
等同于输出'\n'
,再清空缓冲区os.flush()
-
ostream
禁止复制,只允许移动!
文件输入流
- 打开文件
ifstream ifs("input.txt");
ifstream ifs("binary.bin", ifstream::binary);�//以二进制形式打开文件
ifstream ifs;
ifs.open("file")//do something
ifs.close();
-
getline(cin, str)
ifstream
是istream
的子类
故getline(ifs, str)
仍然有效
其他操作:
get()
读取一个字符
ignore(int n=1, int delim=EOF)
丢弃n个字符,或者直至遇到delim分隔符
peek()
查看下一个字符
putback(char c)
返还一个字符
unget()
返还一个字符 - 读入示例:
#include <iostream>
#include <string>
#include <cctype>
#include <fstream>
using namespace std;
int main() {
ifstream ifs("input.txt");
while(ifs) { //判断文件是否到末尾 利用了重载的bool运算符
ifs >> ws; //除去前导空格 ws也是流操纵算子
int c = ifs.peek(); //检查下一个字符,但不读取
if (c == EOF) break;
if (isdigit(c)) //<cctype>库函数
{
int n;
ifs >> n;
cout << "Read a number: " << n << endl;
} else {
string str;
ifs >> str;
cout << "Read a word: " << str << endl;
}
}
return 0;
}
-
scanf
在运行期间需要对格式字符串进行解析
istream
在编译期间已经解析完毕
字符串输入输出流
- 基本概念:
stringstream
是iostream
的子类
iostream
继承于istream
和ostream
stringstream
实现了输入输出流双方的接口 - 构造方式:
stringstream ss;
//空字符串流
stringstream ss(str);
//以字符串初始化流 - 使用时的tips:
可以连接字符串。
可以将字符串转换为其他类型的数据。
配合流操作算子,可以达到格式化输出效果。
注意buffer内容并不是未读取的内容。
示例:
#include <sstream>
using namespace std;
int main() {
stringstream ss;
ss << "10";
ss << "0 200";
int a, b;
ss >> a >> b; //a=100 b=200
return 0;
}
正则表达式
- [ ]内的东西表示各种“或”的选择,()内的东西表范围和优先(使用()也可进行标识,每个标识的内容被称作分组),不带任何括号的内容表示必须选择。
-
\d
等价[0-9],匹配所有单个数字
\w
匹配字母、数字、下划线,等价[a-zA-Z0-9_]
.
匹配除换行以外任意字符
.ar: Thecar
par
ked in thegar
age.
\.
可表示匹配句号
ge.: The car parked in the garage.
x{n,m}
代表前面内容出现次数重复n~m次:
a{4}
匹配aaaa
a{2,4}
匹配aa、aaa、aaaa
a{2,}
匹配长度大于等于2的a
特殊字符:
+
前一个字符至少连续出现1次及以上
a\w+
: The car
parked
in the garage
. - 创建一个正则表达式对象
regex re("^[1-9][0-9]{10}$") ;
11位数
注意:C++的字符串中"\"
也是转义字符
如果需要创建正则表达式"\d+"
,应该写成
regex re("\\d+");
原生字符串:
原生字符串可以取消转义,保留字面值
语法:R"(str)"
表示str的字面值
"\\d+" = R"(\d+)" = \d+
原生字符串能换行,比如
string str = R"(Hello
World)";
结果str = "hello\nWorld"
-
regex_match(s, re)
:询问字符串s是否能完全匹配正则表达式re。 -
regex_match(s, result, re)
:询问字符串s是否能完全匹配正则表达式re,并将捕获结果储存到result中。 -
regex_replace(s, re, s1)
:替换字符串s中所有匹配正则表达式re的子串,并替换成s1
s1也可以使用一些特殊符号,代表捕获的分组
$&
代表re匹配的子串
$1
,$2
代表re匹配的第1/2个分组
- 转义字符:
\n
表示换行、\t
表示制表符 - 范围取反:
[^a-z]
: 匹配所有非小写字母的单个字符
[^c]ar
: The carpar
ked in thegar
age.
^[^0-9][0-9]$
: 匹配长度为2的内容,且第一个不为数字,第二个为数字 - 特殊字符:
\D
等价[^0-9],匹配所有单个非数字
\s
匹配所有空白字符,如\t,\n
\S
匹配所有非空白字符
\W
匹配非字母、数字、下划线,等价[^a-zA-Z0-9_]
^
代表字符串开头,$
代表字符串结尾
如:^\t
只能匹配到以制表符开头的内容
如:^bucket$
只能匹配到只含bucket的内容
?
出现0次或1次
[T]?he:The
car parked in the
garage.
+
至少连续出现1次及以上
c.+e: Thecar parked in the garage
.
*
至少连续出现0次及以上
[a-z]*: The
car
parked
in
the
garage
. - 匹配模式可以使用
'|'
进行连接
(Chapter|Section) [1-9][0-9]?
可以匹配Chapter 1、Section 10等
0\d{2}-\d{8}|0\d{3}-\d{7}
可以匹配010-12345678、0376-2233445
(c|g|p)ar
: Thecar
par
ked in thegar
age.
使用()改变优先级
m|food
可以匹配 m 或者 food
(m|f)ood
可以匹配 mood 或者 food
(T|t)he|car
:The
car
parked inthe
garage. - 分组会按顺序标号:
0号永远是匹配的字符串本身
(a)(pple)
: 0号为apple,1号为a,2号为pple
用(sub)(.*)
匹配subject:0号为subject,1号为sub,2号为ject
如果需要括号,又不想捕获该分组,可以使用(?:pattern)
用(?:sub)(.*)
匹配subject:0号为subject,1号为ject
Lecture 12
智能指针
-
shared_ptr
来自<memory>
库
构造方法:
shared_ptr<int> p1(new int(1));
shared_ptr<MyClass> p2 = make_shared<MyClass>(2);
shared_ptr<MyClass> p3 = p2;
shared_ptr<int> p4; //空指针
- 其他用法:
p.get()
获取裸指针
p.reset()
清除指针并减少引用计数
static_pointer_cast<int>(p)
转为int类型指针(和static_cast类似,无类型检查)
dynamic_pointer_cast<Base>(p)
转为Base类型指针(和dynamic_cast类似,动态类型检查)
注意!不能使用同一裸指针初始化多个智能指针!
int* p = new int();
shared_ptr<int> p1(p);
shared_ptr<int> p2(p);// 会产生多个辅助指针!编译错误!
- 弱引用指针的创建:
shared_ptr<int> sp(new int(3));
weak_ptr<int> wp1 = sp;
- 弱引用指针的用法:
wp.use_count()
//获取引用计数
wp.reset()
//清除指针
wp.expired()
//检查对象是否无效
sp = wp.lock()
//从弱引用获得一个智能指针 -
shared_ptr
涉及引用计数,性能较差
如果要保证一个对象只被一个指针引用,独享所有权,应当使用unique_ptr
#include <memory>
#include <utility>
using namespace std;
int main() {
auto up1 = std::make_unique<int>(20);
//unique_ptr<int> up2 = up1;
//错误,不能复制unique指针
unique_ptr<int> up2 = std::move(up1);
//可以移动unique指针
int* p = up2.release();
//放弃指针控制权,返回裸指针
delete p;
return 0;
}