访问控制限定符

  • public:

    谁都可以访问

  • protected(默认):

    只有自己和派生类可以访问

  • private:

    只有自己可以访问

类和结构体的区别

类有访问限定符,结构体没有

创建类,对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Dog {
string name;
public:
void eat();
};

// 栈区对象
Dog dog1;
Dog dog1[4];

// 堆区对象
Dog* dg1 = new Dog;
dg1->eat();
delete dg1;
dg1 = NULL;

Dog* dg2 = new Dog[3];
dg2[0].eat();
delete[] dg2;
dg2 = NULL;

string 类

  • 查找

    s1.find(查找的字符串,开始位置);

  • 替换

    s1.replace(开始替换位置,替换的位数,替换字符串);

1
2
s1 = "1234567";
s1.replace(s1.find("456"), 2, "abc"); // 123abc67
  • 帮助文档, 选中要查找的关键字,按F1

构造析构

1. 构造

没有返回,函数名和类名相同,不需要显示调用,写在pulic里面

可以有多个重载的构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
Dog* dg = new Dog("mm");
class Dog {
string name;
public:
void eat();

Dog() {
cout << "无参构造" << endl;
}

Dog(string nm) {
name = nm;
cout << "有参构造" << endl;
}

Dog(string nm, int ag) :name(nm), age(ag) {
cout << "初始化表" << endl;
}

~Dog() {
cout << "析构" << endl;
}
};

Dog dog2;
Dog dog1("dd");
Dog* dg = new Dog("mm");
Dog dog3("dy", 10);
Dog dog4{ "dy", 10 };

定义在头文件里定义,函数在cpp文件里定义

1
2
3
void Dog::eat() {
cout << name << endl;
}

2. 析构

析构函数没有参数

堆区变量在delete的时候调用, 栈区变量在作用域结尾调用

3. this指针

this指针用来联系成员函数和具体的事例化对象。

this就是指向当前对象地址的指针

4. 常成员

(1)常成员对象

修饰词const,只能初始化,不能赋值。 所以用初始化列表

1
2
3
4
5
6
class Dog {
const string name;
public:
Dog(string nm) :name(nm) {
}

初始化列表的顺序是按定义的顺序来排的,不是按照初始化列表的顺序。

(2)常成员函数

常量函数里不能改变成员变量的值,主要在编写的时候为了以防编写修改代码

1
2
3
void Dog::eat() const {
cout << name << endl;
}

const 关键词修饰前面的东西,如果前面的没有就修饰后面的

(3)常对象

在对象定义的起那面加 const 定义常对象

常对象只能调用常函数

1
2
mutable Dog dog1;
const Dog dog1;

** mutable 关键字修饰的对象可以在常函数里修改

拷贝构造

右键工程名,添加类。

函数定义一般在头文件里,函数实现在cpp文件里

Complex.h :

1
2
3
4
5
6
7
8
9
10
11
class Complex
{
private:
int m_real;
int m_vir;
public:
Complex(double real);
~Complex();
void print() const;
};

Complex.cpp :

1
2
3
4
5
Complex::Complex(double real)
{
m_real = real;
cout << "类型转换构造" << endl;
}

main.cpp :

1
Complex z = 1.2;

这里用的是类型转换构造(只有单参构造)

这一句不是直接复制,而是1.2先生成了一个Complex类然后再给z生成的。

若要取消这个功能,则要在构造函数定义前加explicit关键字

1
explicit Complex(double real);

拷贝构造

1
2
3
4
5
6
7
8
9
10
Complex::Complex(Complex& that) {
m_real = that.m_real;
m_vir = that.m_vir;
cout << "拷贝构造" << endl;
}

Complex z2 = z;
Complex* pz = new Complex(z2);

Complex* pz2 = pz; // 不是拷贝构造

拷贝构造函数会默认给

1. 浅拷贝

在拷贝的时候只是单纯复制指针,例子如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

class Ninja {
int* m_pAge;
public:
Ninja() {
cout << "默认构造" << endl;
}
explicit Ninja(int age) :m_pAge(NULL) {
if (age > 0) {
m_pAge = new int(age);
}
cout << "单参构造" << endl;
}
Ninja(const Ninja& that) {
m_pAge = that.m_pAge; // 单纯复制指针
}
~Ninja() {
if (m_pAge) {
delete m_pAge;
m_pAge = NULL;
}
cout << "析构" << endl;
}
void Introduce() const {
cout << *m_pAge << endl;
}
};

Ninja* kakaxi = new Ninja(20);
kakaxi->Introduce(); //20
Ninja* n1 = new Ninja(*kakaxi);
n1->Introduce(); //20

delete kakaxi;
n1->Introduce(); //乱码

编译器给的默认拷贝构造是浅拷贝

2. 深拷贝

深拷贝独立分配内存,不会出现浅拷贝的问题。

1
2
3
4
5
Ninja(const Ninja& that) {
//m_pAge = new int(*that.m_pAge); 或者这个
m_pAge = new int;
*m_pAge = *that.m_pAge;
}

友元

可以使外部函数访问内部private数据

1. 友元函数

1
2
3
4
5
6
7
8
9
10
11
12
class Point2D {
friend void print(const Point2D& point); //友元函数定义
friend class Point3D; //友元类
int m_x;
int m_y;
public:
Point2D(int x = 0, int y = 0) :m_x(x), m_y(y) {}
};

void print(const Point2D& point) {
cout << point.m_x << "," << point.m_y << endl;
}

2. 友元类

1
2
3
4
5
6
7
8
9
class Point3D {
Point2D m_p;
int m_z;
Point3D(int x = 0, int y = 0, int z = 0){
m_p.m_x = x; //使得可以访问Point2D的私有成员,需要定义友元类
m_p.m_y = y;
m_z = z;
}
};

注意:

要在每一个用到的类里面声明友元,不然会报错。类要提前声明好。

友元的定义是单向的,(Point2D不可访问Point3D的私有成员)

友元的定义不具有传递性

运算符重载

双目运算符重载

1. 友元函数重载

1
2
3
4
5
6
7
8
9
10
11
12
...
friend Complex operator + (const Complex& cp1, const Complex& cp2);
...
// cp1是左操作数,cp2是右操作数
Complex operator + (const Complex& cp1, const Complex& cp2) {
Complex tp;
tp.m_real = cp1.m_real + cp2.m_real;
tp.m_vir = cp1.m_vir + cp2.m_vir;
return tp;
}


2. 成员函数重载

1
2
3
4
5
6
Complex operator-(const Complex& c) {
Complex tp;
tp.m_real = this->m_real - c.m_real;
tp.m_vir = this->m_vir - c.m_vir;
return tp;
}

this做左操作数,所以只需要写一个参数

单目运算符重载

前++

1
2
3
4
5
Complex& Complex::operator++() {
this->m_real++;
this->m_vir++;
return *this;
}

<< 运算符只能用友元重载

1
2
3
4
ostream& operator<<(ostream& out,Complex& c) {
out << c.m_real << "+" << c.m_vir << "i";
return out;
}

还有>>

1
2
3
4
istream& operator>>(istream& in, Complex& c) {
in >> c.m_real >> c.m_vir;
return in;
}

注意在.h文件里也要加上需要的头文件

后++, 需要用一个哑元来区分(规定这样)

1
2
3
4
5
6
Complex Complex::operator ++ (int) {
Complex tp = *this;
this->m_real++;
this->m_vir++;
return tp;
}

三目运算符不能重载

=,(),[],->,->* 必须是成员函数

双目运算符建议友元重载,单目运算符建议成员重载

拷贝赋值

由于存在浅拷贝的问题,所以需要自己实现拷贝函数

由于需要作左值,所以返回值要是个地址

  1. 避免自赋值
  2. 分配新资源
  3. 拷贝新内容
  4. 释放旧资源
  5. 返回自引用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
cArray& operator = (const cArray& that) {
cout << "拷贝复制构造" << endl;
if (&that != this) *this;
int *ptp = new int[that.m_size];
memcpy(ptp, that.m_array, that.m_size*sizeof(that.m_array[0]));
if (m_array) {
delete[] this->m_array;
m_array = NULL;
}
swap(ptp, this->m_array);
delete[] ptp;
ptp = NULL;
return *this;
}

拷贝赋值尽可能赋值构造析构的代码

1
2
3
4
5
6
7
cArray& operator = (const cArray& that) {
cout << "拷贝复制构造" << endl;
if (&that != this) *this;
cArray temp(that);
swap(this->m_array, temp.m_array);
return *this;
}

静态成员

静态成员属于类,不属于对象。声明周期进程级。(全局的)

相当于全局变量,知识多了一个类的作用域和访问权限

静态成员变量

静态成员变量定义只能在类外面,不能在构造函数初始化

1
2
3
static double m_rate; //类里

double Account::m_rate = 0.2; //类外

静态成员函数

1
2
3
4
5
static void adjust(double rate) {
m_rate = rate; //没有this指针
}

Account::adjust(0.3);

单例

只能实例化一个对象

饿汉模式:程序启动就创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Single {
int m_data;
private:
Single(int data):m_data(data){}
Single(const Single&){}
static Single s_instance;
public:
static Single& getInstance() {
return s_instance;
}
};

Single Single::s_instance(100);

Single& s1 = Single::getInstance(); // s1和s2是一个对象
Single& s2 = Single::getInstance();

懒汉模式:用的时候再创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Singleton {
int m_data;
private:
Singleton(int data) :m_data(data){}
static Singleton* s_instance;
public:
static Singleton& getInstance(int data=0) {
if (!s_instance){
s_instance = new Singleton(data);
}
return *s_instance;
}

static void clear() {
if (!s_instance) {
delete s_instance;
s_instance = NULL;
}
}
};

Singleton* Singleton::s_instance = NULL;

成员指针

成员变量再对象中的相对地址。指向成员变量的指针是成员指针

1
2
int Integer::*pvalue = &Integer::m_i;
i1.*pvalue = 50;

成员指针是偏移量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class A{
public:
int m_a;
int m_b;
A(int a = 10, int b = 20) :m_a(a), m_b(b){}
};


A a;
int A::*p = NULL;
cout << *(int*)&p << endl; //-1
p = &A::m_a;
cout << *(int*)&p << endl; //0
p = &A::m_b;
cout << *(int*)&p << endl; //4

去常

1
2
3
int i = 4;
const int *pc1 = &i; // 不能通过指针修改i
int *p1 = const_cast<int*>(pc1); //去常

可以 改int const 变量的值,因为int const 声明的变量不是在常量区的,变量不能改,但是里面的值是可以改动的

1
2
3
4
5
int const i = 5;
int const *p = &i;
int *k = const_cast<int*>(p);
*k = 10;
cout << i << endl << *k << endl << *p << endl; //5 10 10

因为它是从寄存器里读取值,那个时候寄存器还是原来的i值,所以是5

用volatile告诉编译器它是易变的

1
2
3
4
5
volatile int const i = 5;
volatile int const *p = &i;
int *k = const_cast<int*>(p);
*k = 10;
cout << i << endl << *k << endl << *p << endl; // 10 10 10

继承

基类(父类)和派生类(子类)。共性和个性

public 公有继承

不改变父类的属性

子类存在但不能访问父类的私有成员,可以访问受保护和共有成员

同名隐藏:在子类若有和父类同名的函数,那么调用子类的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Human {
int m_age;
string m_name;
public:
Human(int age = 20, string name = "fy") :m_age(age), m_name(name){
cout << "父类构造" << endl;
}
void eat() {
cout << "eat" << endl;
}
void sleep() {
cout << "sleep" << endl;
}
};

class Teacher :public Human { //公有继承
public:
Teacher() {
cout << "teacher构造" << endl;
}
void teach() {
cout << "teach" << endl;
}
void eat() { //同名隐藏,eat调用子类的函数
cout << "T_eat" << endl;
}
};

截然性:子类对象可以在任何时候看成基类。

1
2
3
Teacher b;
Human *p1; //用父类的指针指向子类
p1 = &b;

因为构造子类的时候会先构造基类,所有有个基类子对象,地址和子类相同。

这样操作减少了访问范围。

protected 保护继承

基类的public成员变成protected成员

private 私有继承

基类的所有成员都变成private成员

改变成员属性,不能改变父类的private

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Teacher :public Human { //给了所有权限
protected:
using Human::eat; // 改变public 变成 protected
public:
Teacher() {
cout << "teacher构造" << endl;
}
void teach() {
cout << "teach" << endl;
}
void eat() { //同名隐藏
cout << "T_eat" << endl;
}
};

阻断继承

把构造函数写成私有成员,就不能继续继承了

1
2
3
4
5
6
class A{};
class B :public A{};
class C :public B{
C(){}
};
class D :public c{}; // 编译错误

另外,父类的构造不能被子类继承

在继承的时候会调用父类默认构造函数

若需要调用别的构造函数,则需要显示调用,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class A{
int m_data;
public:
A(){
cout << "默认构造" << endl;
}
A(int a) :m_data(a){
cout << "单参构造" << endl;
}
};

class B :public A{
int m_b;
public:
B(int x) :A(10), m_b(x) {
cout << "B的构造" << endl;
}
};

多重继承

一个子类继承了多个父类

在分配内存的时候,从左到右分配内存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

class Telephone{
public:
Telephone() {
cout << "Telephone构造" << endl;
}
void call() {
cout << "call" << endl;
}
};

class Camera {
public:
Camera() {
cout << "Camera构造" << endl;
}
void takephoto() {
cout << "photo" << endl;
}
};

class IphoneXMax : public Telephone, public Camera {
public:
IphoneXMax() {
cout << "iphone构造" << endl;
}
};

此时,用截然性,用不同的父类指针指向子类会得到不同的地址

1
2
Telephone *p1 = &ip;
Camera *p2 = &ip;

因为子类会隐式转换成基类,从而实现截然性

菱形继承

X和Y继承A,B继承X和Y

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class A{
public:
A() {
cout << 'A' << endl;
}
void func() {
cout << "func" << endl;
}
};

class X :public A{
public:
X() {
cout << "x" << endl;
}
};

class Y :public A {
public:
Y() {
cout << "Y" << endl;
}
};
class B : public X, public Y{
public:
B() {
cout << "b" << endl;
}
};


B b;
b.func(); //报错,func的调用不明确
b.X::func(); //需要这么写才正确

虚继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class X :virtual public A{
public:
X() {
cout << "x" << endl;
}
};

class Y :virtual public A {
public:
Y() {
cout << "Y" << endl;
}
};

B b;
b.func();

这样就可以避免重复生产A,导致调用的不明确

虚继承会产生一个共用的A

并且虚继承的类会有一个指针指向虚基表(一个数组),里面储存的是A对象的偏移值

一个小问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Animal {
public:
void say() {
cout << "?DF??ds" << endl;
}
};

class Dog :public Animal{
public:
void say() {
cout << "汪汪汪" << endl;
}
};

Animal *p = new Animal();
p->say(); // ?DF??ds
delete p;
p = new Dog();
p->say(); // ?DF??ds
delete p;
p = NULL;

因为截然性,所以Animal 的指针可以指向 Dog

但是它不能用Dog的成员函数

要用多态来解决这个问题

多态

虚函数

把基类的say()声明成虚函数,之后狗就会汪汪叫,符合了人类的逻辑

1
2
3
4
5
6
class Animal {
public:
virtual void say() {
cout << "?DF??ds" << endl;
}
};
  • 当子类的函数形式(返回类型,函数名,参数表)和父类的虚函数一样的时候,子类的该函数也会默认成为虚函数(无论加不加virtual)

此时,子类的虚函数会覆盖(重写)父类的虚函数(和隐藏不同)

  • 只有成员函数(非静态)可以覆盖

使用虚函数后,基类指针会根据实际指向类型来分别调用不同的函数

若不适用,基类指针会根据指针的类型来调用函数

这个现象称为多态

多态函数

单态函数:一个函数对应一个功能

多态函数:一个函数多个功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int add(int a, int b) {
return a + b;
}
int mul(int a, int b) {
return a*b;
}
int divi(int x, int y) {
return x / y;
}
int calc(int x, int y, int(*fun)(int, int)) {
return fun(x, y);
}

cout << calc(1, 2, add) << endl;
cout << calc(1, 2, mul) << endl;
cout << calc(1, 4, divi) << endl;

虚析构

父类指针指向子类的时候,要用虚析构,避免子类个性成员内存泄漏

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class A{
public:
A() {
cout << "构造" << endl;
}
virtual ~A() { // 若不加,那么B会少个析构
cout << "析构" << endl;
}
};
class B :public A{
int* p;
public:
B() {
cout << "B构造" << endl;
p = new int(10);
}
~B() {
cout << "B析构" << endl;
delete p;
p = NULL;
}
};


A* a = new B;
delete a;

纯虚函数和抽象类

1
virtual void foo(int) = 0; //纯虚函数/抽象方法

若类有一个纯虚函数,那么这个类是抽象类

抽象类不能实例化对象

若一个类所有的函数都是纯虚函数,那么这个类是纯抽象类(接口类)

纯抽象类只用来提供接口。

若一个类太抽象,一般写成接口类

在继承的时候子类必须对基类形成完全覆盖,否则也会称为抽象类,而不能实例化对象。

IO流

C++是一个单独的语言,有单独的语法。C++兼容C,但是比C功能强大

用C的输出不能满足C++的需求

流格式化

头文件

切换输出格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
int a = 21;
cout.setf(ios::showbase); //为整数加一个进制前缀
cout << "dec " << a << endl;
cout.unsetf(ios::dec); //取消10进制格式
cout.setf(ios::hex); // 设置16进制格式
cout << "hex: " << a << endl;
cout.unsetf(ios::hex);
cout.setf(ios::oct);
cout << "oct: " << a << endl;

const char *pt = "sirius";
//直接利用格式成员函数
cout.width(10); //指定字符域宽, 一次
cout << pt << endl; // " sirius"

cout.width(10);
cout.fill('*');
cout << pt << endl;

double pi = 22.0 / 7;
cout << pi << endl;
cout.setf(ios::scientific); //科学计数法
cout << pi << endl;
cout.unsetf(ios::scientific);

//四个字符宽度 右对齐 用'0'填充
cout << setw(4) << right << setfill('0') << 1 << endl;

cout << showbase << oct << 21 << endl; //八进制输出

bool b = false;
cout << b << endl;
cout << boolalpha << b << endl; // 布尔用字母输出,开关
cout << noboolalpha << b << endl;

cout ,cerr 和 clog

cout 标准输出对象(有缓冲区), 可以重定向到一个文件里

cerr 标准错误流(无缓冲区),必须显示在显示器上

clog 标准错误流 (有缓冲区)

标准输入输出流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
cout << "a";
cout << "b" << ends; // \0
cout << "c" << endl;// \n + flush
cout << "d" << flush; //刷新缓冲区

//获取单个字符
char c = cin.get();// 输单个字符后换行
cout << c << endl;

cout.put(c);
endl(cout << "hello");
cout << "world" << endl;

//获取行
char str[200] = { 0, };
cin.getline(str,20,'/'); //读取最大20个,遇到'/'就停止
cout << str;

字符串流

头文件

把字符串转换成数字

1
2
3
4
5
6
string res = "100";
int num = 0;
stringstream sst;
sst << res;
sst >> num;
cout << num << endl;

分割提取字符+类型转换

1
2
3
4
5
string ip = "192.168.0.1";
stringstream sst2(ip);
int a1, a2, a3, a4;
char ch;
sst2 >> a1 >> ch >> a2 >> ch >> a3 >> a4;

拼接字符串+类型转换

1
2
3
4
5
stringstream sst3;
int num = 3;
char str[] = "14159";
sst3 << num << ch << str;
cout << sst3.str() << endl;

文件流

头文件

读写文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
struct Student {
char name[20];
int num;
int age;
char sex;
};

Student stu[3] =
{ "a", 1001, 18, 'm', "Really", 1002, 24, 'f', "dd", 1003, 19, 'm' };

//写入文件(二进制)
ofstream outfile("stu.dat", ios::binary);
if (!outfile) {
cerr << "open error!" << endl;
abort();
}
for (int i = 0; i < 3; i++) {
outfile.write((char*)&stu[i], sizeof(stu[i]));
}
outfile.close();

//读取文件
Student rStu[3];
ifstream infile("stu.dat", ios::binary);
if (!infile) {
cerr << "open error!" << endl;
abort();
}
for (int i = 0; i < 3; ++i) {
infile.read((char*)&rStu[i], sizeof(rStu[i]));
}
infile.close();

//测试
for (int i = 0; i < 3; ++i) {
cout << "name:" << rStu[i].name << endl;
cout << "nu:" << rStu[i].num << endl;
cout << "age:" << rStu[i].age << endl;
cout << "sex:" << rStu[i].sex << endl;
}

把三个文件合起来,又分开

注意要在文件之前储存信息,方便分离

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
struct FileInfo {
char filename[20];
int filesize;
};

void pack() {
FileInfo fileList[3] = {
{ "1.png", 0 },
{ "2.png", 0 },
{ "3.png", 0 } };
fstream file[3];

for (int i = 0; i < 3; ++i) {
file[i].open(fileList[i].filename, ios::in | ios::binary);
file[i].seekg(0, ios::end);
fileList[i].filesize = file[i].tellp();
file[i].seekg(0, ios::beg);
}

fstream newfile("backups.dat", ios::out | ios::binary);
newfile.write((char*)fileList, sizeof(fileList));

char *tmp = new char[800000];
for (int i = 0; i < 3; ++i) {
file[i].read(tmp, fileList[i].filesize);
newfile.write(tmp, fileList[i].filesize);
}
delete tmp;
tmp = NULL;

for (int i = 0; i < 3; ++i) file[i].close();
newfile.close();
}

void unpack() {
FileInfo pic[3];

fstream file("backups.dat", ios::in | ios::binary);
file.read((char*)pic, sizeof(pic));
char *p = new char[800000];
for (int i = 0; i < 3; ++i) {
file.read(p, pic[i].filesize);
char tp[20] = "out_";
strcat(tp, pic[i].filename);
fstream outfile(tp ,ios::out | ios::binary);
outfile.write(p, pic[i].filesize);
outfile.close();
}
delete p;
p = NULL;
file.close();
}

异常

  1. 利用返回值的不同来判断

    缺点:需要逐层判断

  2. setjmp 和longjmp

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    #include <setjmp.h>

    jmp_buf j_err;

    void func3() {
    FILE* pFile;
    if (!(pFile = fopen("null", "r"))) {
    cout << "调用longjmp\n";
    longjmp(j_err, -1);
    cout << "调用longjmp后" << endl;
    }
    return;
    }

    void func4() {
    cout << "调用func3前" << endl;
    func3();
    cout << "调用func3前" << endl;
    }

    if (setjmp(j_err) == 0) {
    cout << "第一次调用setjmp" << endl;
    func4();
    }
    else{
    cout << "第二次调用setjmp\n";
    }
    /***
    *第一次调用setjmp
    调用func3前
    调用longjmp
    第二次调用setjmp
    */
  3. 抛出异常

    throw 抛出异常

    try { } catch (参数) { }

    只能捕获一个异常,出现异常后马上就会捕获

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    void func3() {
    FILE* pFile;
    if (!(pFile = fopen("null", "r"))) {
    throw - 1;
    }
    char *pb = (char*)malloc(0xfeeefeee);
    if (!pb) {
    throw "内存申请失败";
    }
    return;
    }

    try {
    func3();
    }
    catch (int ex) {
    if (-1 == ex) {
    cout << "文件打开失败" << endl;
    }
    }
    catch (const char* ex) {
    cout << ex << endl;
    }

    捕获异常的顺序从上往下匹配

标准库异常

overflow_error 是一个标准库的异常类

exception是它的基类

若不知道异常的种类,可以写exception

其他的异常可以自己写个类,继承exception类

1
2
3
4
5
6
7
8
9
10
void push(int data) {
throw overflow_error("堆栈上溢");
}

try {
push(10);
}
catch (exception &ex) {
cout << ex.what() << endl;
}

继承标准库异常类

1
2
3
4
5
6
7
8
9
10
11
class Overflow: public exception
{
public:
const char* what() const throw() {
return "堆栈撑不住了";
}
};

void push(int data) {
throw Overflow();
}

RTTI

​ static_cast<目标类型>(原类型变量)

  1. 隐式类型转换的逆转换
1
2
3
double adouble = 11.11;
void *pv = static_cast<void *>(&adouble);
double *pd = static_cast<double *>(pv); // 要加static_cast才可以转
  1. 动态类型转换
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class A {
public:
virtual void foo() {
cout << "A::foo()" << endl;
}
};

class B :public A {
public:
void foo() {
cout << "B::foo()" << endl;
}
};


B b;
A *pa = &b; //上行转换 安全

A a;
B *pb = static_cast<B*>(&a); // 下行转换,不安全

cout << typeid(pa).name() << endl; // 运行时类型识别的函数 class A
cout << typeid(pb).name() << endl; // class B

B *pb1 = dynamic_cast<B*>(&a); // 在有虚函数的前提下,动态类型转换 安全
// dynamic_cast 会引发动态类型识别, 使用typeid比较声明类型和实际类型
// 对于指针 不一致的返回null 对于引用的比较,不一致的抛出bad_typeid异常

把父类转子类要用dynamic_cast<>()转换

C++11

新增类型

long long 8字节

unsigned long long 8字节

char16_t 2字节

char32_t 4字节

nullptr 空指针

1
int *p = nullptr;

类型别名

可以用于给长名字取别名

1
2
using dtype = int;
dtype dt = 20;

auto

1
2
3
auto ai = 10;
auto as = "Hello";
cout << typeid(as).name() << endl;

初始化

1
2
3
4
5
6
7
class A {
public:
int m_a{ 10 };
};

int num{ 10 };
int arr[]{1, 2, 3, 4, 5};

范围for循环

1
2
3
for (auto i : arr) {
cout << i << " ";
} // 1 2 3 4 5

返回类型后置

1
2
3
auto add(int a, int b)->int {
return a + b;
}

默认函数和已删除函数

1
2
3
4
5
class A {
public:
A() = default;
A(const A& that) = delete;
};

委托构造函数

1
2
3
4
5
6
7
8
9
class B {
int m_a;
int m_b;
int m_c;
public:
B(int a, int b, int c) :m_a(a), m_b(b), m_c(c){}
B() :B(10, 20, 30){} // 无参构造后面调用三参构造
B(int a) :B(a, 20, 30){}
};

右值引用

1
2
3
4
int num = 10;
int &lnum = num; // 左值引用
int &&rnum = 10; // 右值引用
//用处 移动语义 移动构造

lamda表达式(匿名函数)

1
2
auto sum = [](int a, int b){return a + b; };
cout << sum(1, 3) << endl;

override和final

override确保成功覆盖

类被final修饰不能被继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class A {
public:
virtual void foo() {
cout << "A::foo()" << endl;
}
virtual void bar() final{}
};

class B :public A {
public:
void foo() override {
cout << "B::foo()" << endl;
}
};

函数模板

泛型

可以把类型作为参数传进去

1
2
3
4
5
6
7
8
template<typename T> // 模板头

T max(T a, T b) {
return a > b ? a : b;
}

cout << max<int>(3, 4) << endl;
cout << max(3, 4) << endl; // 隐式推断类型
1
template<typename T,typename A> // 2各以上的模板头

类模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
template<typename T>
class Stack {

list<T> m_list;
public:
Stack() {
m_list.clear();
}
~Stack() {
m_list.clear();
}
Stack(Stack<T> const& that) :m_list(that.m_list){}
Stack <T>& operator=(Stack<T> const& that) {
if (&that != this) m_list = that.m_list;
return *this;
}
void push(T const& data);
void pop();
T& top();
T const& top() const;
bool empty() const;
};

//类外定义要带上模板头
template<typename T>
void Stack<T>::push(T const& data) {
m_list.push_back(data);
}

template<typename T>
void Stack<T>::pop() {
if (empty()) {
throw underflow_error("堆栈下溢");
}
m_list.pop_back();
}

template<typename T>
T& Stack<T>::top() {
if (empty()) {
throw underflow_error("堆栈下溢");
}
return m_list.back();
}

template<typename T>
T const& Stack<T>::top() const {
return const_cast<Stack<T>*>(this)->top(); // this指针有常属性,要去常
}

template<typename T>
bool Stack<T>::empty() const {
return m_list.empty();
}

// main
try {
Stack<int> s1;
for (int i = 0; i < 10; ++i) {
s1.push(i);
}
while (!s1.empty()) {
cout << setw(2) << left << s1.top();
s1.pop(); // 9 8 7 6 5 4 3 2 1 0
}
cout << endl;
}
catch (exception &ex) {
cout << ex.what() << endl;
getchar();
return -1;
}

类模板是二次编译,所以声明和定义要写在同一个文件里,建议在头文件里

1
friend ostream& operator << <T>(ostream& out, Stack<T>& stack);

友元声明的时候要加泛型支持,在名字后面加

模板类继承

父类自定义了构造函数,子类必须要用初始化列表初始化

继承的时候,如果子类不是模板类,必须指明父类的类型

如果子类是模板类,要么指明父类的类型,要么用子类的泛型来指定父类

模板特化

函数模板的完全特化

1
2
3
4
5
6
7
8
9
10
11
template <typename T>
T max(const T a, const T b) {
return a > b ? a : b;
}

template <typename T>
const char* max(const char* a, const char* b) {
return strcmp(a, b) > 0 ? a : b;
}

//这样比较字符串就不会比较指针了

类模板的完全特化

1
2
3
class Stack<bool> {
//...
}

类模板的偏特化

1
2
3
4
5
6
7
8
9
template <typename T,typename Allocator>
class vector {
//...
}

template <typename Allocator>
class vector<bool,Allocator> {
//...
}

C++17

安装

安装 在这里下载 https://nuwen.net/mingw.html

image-20200928172348197

安装完后添加环境变量即可

image-20200928173830999

如果有变量冲突的话,可以用 where gcc 查看

编译

1
g++ [file.cpp] --std=c++17 -o file

for

1
auto [v, len] = G[u][k-1]; // pair

初始化

1
2
vector<vector<pair<int, int>>> G(n+1, vector<pair<int,int>>());
vector<vector<vector<int>>> dp(2, vector<vector<int>>(n+1, vector<int>(m+1, -oo)));

函数

1
2
3
4
5
6
7
8
9
10
function<int(int,int)> dfs = [&](int x, int fa) -> int {
d[x] = 0;
for (auto [v, len] : G[x]) if (v != fa) {
int td = dfs(v, x);
if (td + len > d[x]) {
d[x] = td + len;
}
}
return d[x];
};