GuoXin Li's Blog

CPP refresh

字数统计: 21.7k阅读时长: 100 min
2021/02/22 Share

CPP refresh

内存模型

代码区

存放函数体的二进制代码,由操作系统进行管理

全局区

存放全局变量和静态变量以及常量.

栈区

  • 由编译器自动分配释放
  • 存放函数的参数值
  • 存放局部变量

堆区

由程序员分配和释放,若不手动释放,最后将由操作系统回收

程序运行前

程序编译后,生成 exe 可执行程序,未执行该程序前分为两个区域

  • 代码区:
    • 存放 CPU 执行的机器指令
    • 代码区是共享的,共享的的目的是对于频繁被执行的程序,只需要在内存中由一份代码即可
    • 代码区为 只读,防止程序意外地修改了它的指令
  • 全局区
    • 全局变量和静态变量存放在此
    • 常量区
    • 字符串
    • 其他常量

==该区域的数据在程序结束后由操作系统释放==

程序运行后

栈区:由编译器自动分配释放,存放函数参数值,局部变量

注意事项:不要返回局部变量的地址,栈区开辟的数据由编译器自动释放

new 操作符

CPP 利用 new 操作符在堆区开辟数据

语法:new 数据类型

==利用 new 创建的数据,会返回该数据对应的类型的指针==

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>
using namespace std;

int* func(){
int* a = new int(10);
return a;
}

int main(int argc, char** argv){
int *p = func();
cout << *p << endl;
cout << *p << endl;
cout << *p << endl;
delete p;

return 0;
}

开辟数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//堆区开辟数组
int main() {

int* arr = new int[10];

for (int i = 0; i < 10; i++)
{
arr[i] = i + 100;
}

for (int i = 0; i < 10; i++)
{
cout << arr[i] << endl;
}
//释放数组 delete 后加 []
delete[] arr;


return 0;
}

引用

引用是可以作为函数返回值存在的

注意不要返回局部变量引用

函数变量可以作为左值,则必须为返回引用类型(指针)

引用可以作为函数的返回值存在

⚠️:不要在函数里返回局部变量

如果函数作为左值,则此被作为左值的函数必须返回为引用类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
using namespace std;

int& quoteTest(){
static int a = 10;
return a;

}
//如果函数作为左值,则此被作为左值的函数必须返回为引用类型
int main(int argc, char** argv){
int& ret = quoteTest();
cout<<"a: "<< ret<<endl;
cout<<"a: "<< ret<<endl;
quoteTest() = 1000;
cout<<"modified ret: "<< ret<<endl;
cout<<"modified ret: "<< ret<<endl;

return 0;
}

引用的本质

本质:引用的本质在 C++ 内部实现是一个指针常量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//发现是引用,转换为 int* const ref = &a;
void func(int& ref){
ref = 100; // ref是引用,转换为*ref = 100
}
int main(){
int a = 10;

//自动转换为 int* const ref = &a; 指针常量是指针指向不可改,也说明为什么引用不可更改
int& ref = a;
ref = 20; //内部发现ref是引用,自动帮我们转换为: *ref = 20;

cout << "a:" << a << endl;
cout << "ref:" << ref << endl;

func(a);
return 0;
}

常量引用

==常量引用主要用来修饰形参,防止误操作==

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
using namespace std;

void showValue(const int & val){
cout << val << endl;
}

int main(int argc, char** argv){
int temp = 10;
const int& test = temp;
showValue(temp);
showValue(test);

return 0;
}

函数提高

函数占位参数示例

1
2
3
4
5
6
7
8
void func(int a, int) {
cout <<"pass"<<;
}
int main(int argc, char**argv){
func(10, 10); //占位符参数必须进行填补

returen 0;
}

函数重载

函数名字可以相同,提高复用性。

重载满足条件:

  • 同一个作用域下
  • 函数名称相同
  • 函数的参数类型不同,或者个数不同,或者顺序不同

注意:函数的返回值不可以作为函数重载的条件

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
//函数重载需要函数都在同一个作用域下
void func()
{
cout << "func 的调用!" << endl;
}
void func(int a)
{
cout << "func (int a) 的调用!" << endl;
}
void func(double a)
{
cout << "func (double a)的调用!" << endl;
}
void func(int a ,double b)
{
cout << "func (int a ,double b) 的调用!" << endl;
}
void func(double a ,int b)
{
cout << "func (double a ,int b)的调用!" << endl;
}

//函数返回值不可以作为函数重载条件
//int func(double a, int b)
//{
// cout << "func (double a ,int b)的调用!" << endl;
//}


int main() {

func();
func(10);
func(3.14);
func(10,3.14);
func(3.14 , 10);

system("pause");

return 0;
}

函数重载注意事项

  • 引用作为重载条件
  • 函数重载碰到函数默认参数
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

// 1. 引用作为重载条件
void func(int &a) // int &a = 10 不合法
{
cout << "func (int &a)" << endl;
}

void func(const int &a) //const int &a = 10 合法
{
cout << "func(const int &a)" << endl;
}

// 2. 函数重载碰到函数默认参数
void funcc(int a, int b = 10)
{
cout << "..." << endl;
}
void funcc(int a )
{
cout << "..." << endl;
}

int main(){
int a = 10;
// 1.
func(a); // 调用无 const
func(10); // 调用有 const

// 2.
// funcc(100); //产生歧义

return 0;
}

类和对象

==封装、继承、多态==

设计一个圆,求圆的周长

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const double PI = 3.1415926535;

class Circle
{
public:
int radius;
double calculatePT()
{
return 2 * PI * radius;
}
};

int main()
{
Circle cl;
cl.radius = 10;
cout << "perimeter is: " << cl.calculatePT()<<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
class Student
{
public:
void setName(String name)
{
this->name = name;
}
void setId(int id)
{
this->id = id;
}
void showStudent()
{
cout << "name: "<< name << "id: "<< id<<endl;
}
public:
String name;
int id;
};

int main()
{
Student stu;
stu.setName("nike");
stu.setId(123);
stu.showStudent();

return 0;
}

封装权限:

public :公共权限,类内可以访问,类外也可以访问

protected:保护权限,类内可以访问,类外不可以访问

private:私有权限,类内可以访问,类外不可以访问

struct 与 class 区别

二者的唯一区别在于默认的访问权限的不同

  • struct 为默认公共
  • class 为默认私有
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class C1
{
int m_A; //默认是私有权限
};

struct C2
{
int m_A; //默认是公共权限
};

int main() {

C1 c1;
c1.m_A = 10; //错误,访问权限是私有

C2 c2;
c2.m_A = 10; //正确,访问权限是公共

system("pause");

return 0;
}

对象的初始化和清理

C++ 利用构造函数和析构函数解决对象的初始化和清理两个重要问题。

如果不手动提供构造函数和析构函数,编译器会提供

构造函数语法:类名(){}

  • 构造函数,没有返回值,不写void
  • 函数名称与类名称相同
  • 构造函数可以有参数,可以发生重载
  • 程序在调用对象时候会自动调用构造,且只调用一次

析构函数语法:~类名(){}

  • 析构函数没有返回值不写void
  • 函数名与类名相同,前加 ~
  • 析构函数不可以有参数,不发生重载
  • 程序在对象销毁前自动调用析构,无需手动调用,且只调用一次
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
class Person
{
public:
//构造函数
Person()
{
cout << "Person的构造函数调用" << endl;
}
//析构函数
~Person()
{
cout << "Person的析构函数调用" << endl;
}

};

void test01()
{
Person p;
}

int main() {

test01();


return 0;
}

构造函数中的拷贝构造函数

1
2
3
4
5
6
7
8
9
10
class Person{
public:
Person(const Person& p)
{
age = p.age;
cout << "拷贝构造函数"<< endl;
}
public:
int age;
};

//注意1:调用无参构造函数不能加括号,如果加了编译器认为这是一个函数声明

//注意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
//1、构造函数分类
// 按照参数分类分为 有参和无参构造 无参又称为默认构造函数
// 按照类型分类分为 普通构造和拷贝构造

class Person {
public:
//无参(默认)构造函数
Person() {
cout << "无参构造函数!" << endl;
}
//有参构造函数
Person(int a) {
age = a;
cout << "有参构造函数!" << endl;
}
//拷贝构造函数
Person(const Person& p) {
age = p.age;
cout << "拷贝构造函数!" << endl;
}
//析构函数
~Person() {
cout << "析构函数!" << endl;
}
public:
int age;
};

//2、构造函数的调用
//调用无参构造函数
void test01() {
Person p; //调用无参构造函数
}

//调用有参的构造函数
void test02() {

//2.1 括号法,常用
Person p1(10);
//注意1:调用无参构造函数不能加括号,如果加了编译器认为这是一个函数声明
//Person p2();
//Person p3(p2);
//2.2 显式法
Person p2 = Person(10);
Person p3 = Person(p2);
Person(10)//单独写就是匿名对象 当前行结束之后,马上析构

//2.3 隐式转换法
Person p4 = 10; // Person p4 = Person(10);
Person p5 = p4; // Person p5 = Person(p4);

//注意2:不能利用 拷贝构造函数 初始化匿名对象 编译器认为是对象声明
//Person p5(p4);
}

int main() {

test01();
//test02();

system("pause");

return 0;
}

拷贝构造函数调用时机

  • 使用一个已经创建完毕的对象来初始化一个新对象

  • 值传递的方式给函数参数传值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    //2. 值传递的方式给函数参数传值
    //相当于Person p1 = p;
    void doWork(Person p1) {

    }
    void test02() {
    Person p; //无参构造函数
    doWork(p);
    }
  • 以值方式返回局部对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    //3. 以值方式返回局部对象
    Person doWork2()
    {
    Person p1;
    cout << (int *)&p1 << endl;
    return p1;
    }

    void test03()
    {
    Person p = doWork2();
    cout << (int *)&p << endl;
    }

Screen Shot 2021-03-01 at 19.22.52

构造函数调用规则

默认情况下,c++编译器至少给一个类添加3个函数

1.默认构造函数(无参,函数体为空)

2.默认析构函数(无参,函数体为空)

3.默认拷贝构造函数,对属性进行值拷贝

构造函数调用规则如下:

  • 如果用户定义有参构造函数,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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
class Person {
public:
//无参(默认)构造函数
Person() {
cout << "无参构造函数!" << endl;
}
//有参构造函数
Person(int age ,int height) {

cout << "有参构造函数!" << endl;

m_age = age;
m_height = new int(height);
}
//拷贝构造函数
Person(const Person& p) {
cout << "拷贝构造函数!" << endl;
//如果不利用深拷贝在堆区创建新内存,会导致浅拷贝带来的重复释放堆区问题
m_age = p.m_age;
//m_Height = p.m_Height 编译器默认创建的拷贝函数中的对于指针 m_height 的值拷贝
m_height = new int(*p.m_height); //自己实现的在堆区重新开辟空间拷贝

}

//析构函数
~Person() {
cout << "析构函数!" << endl;
if (m_height != NULL)
{
delete m_height;
}
}
public:
int m_age;
int* m_height; //使用指针进行存储,将身高的数据开辟到堆区
};

void test01()
{
Person p1(18, 180);

Person p2(p1);

cout << "p1的年龄: " << p1.m_age << " 身高: " << *p1.m_height << endl;

cout << "p2的年龄: " << p2.m_age << " 身高: " << *p2.m_height << endl;
}

int main() {

test01();

system("pause");

return 0;
}

image-20210301201817114

总结:如果属性有在 堆区 开辟的,一定要自己提供拷贝函数,防止浅拷贝带来的问题

初始化列表

1
2
3
4
5
6
7
8
9
10
11
12
	//初始化列表方式初始化
Person(int a, int b, int c) :m_A(a), m_B(b), m_C(c) {}
void PrintPerson() {
cout << "mA:" << m_A << endl;
cout << "mB:" << m_B << endl;
cout << "mC:" << m_C << endl;
}
private:
int m_A;
int m_B;
int m_C;
};

Screen Shot 2021-03-02 at 00.12.47

类对象作为类成员

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
class Phone
{
public:
Phone(string name)
{
m_PhoneName = name;
cout << "Phone构造" << endl;
}

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

string m_PhoneName;

};


class Person
{
public:

//初始化列表可以告诉编译器调用哪一个构造函数
Person(string name, string pName) :m_Name(name), m_Phone(pName)
{
cout << "Person构造" << endl;
}

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

void playGame()
{
cout << m_Name << " 使用" << m_Phone.m_PhoneName << " 牌手机! " << endl;
}

string m_Name;
Phone m_Phone;

};
void test01()
{
//当类中成员是其他类对象时,我们称该成员为 对象成员
//构造的顺序是 :先调用对象成员的构造,再调用本类构造
//析构顺序与构造相反
Person p("张三" , "苹果X");
p.playGame();

}


int main() {

test01();

system("pause");

return 0;
}
//初始化列表可以告诉编译器调用哪一个构造函数
Person(string name, string pName) :m_Name(name), m_Phone(pName)
{
    cout << "Person构造" << endl;
}

Phone m_Phone;

以上两处,构造函数中 pName 为 string 类型, m_Phone 为 Phone 对象类型,其实是构造函数的隐式调用

// Phone m_Phone = pName;

等价于以下:

// Phone m_Phone = Phone(pName);

静态成员

静态成员就是在成员变量和成员函数前加上关键字static,称为静态成员

静态成员分为:

  • 静态成员变量
    • 所有对象共享同一份数据
    • 在编译阶段分配内存
    • 类内声明,类外初始化
  • 静态成员函数
    • 所有对象共享同一个函数
    • 静态成员函数只能访问静态成员变量

静态成员变量:

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
class Person
{

public:

static int m_A; //静态成员变量

//静态成员变量特点:
//1 在编译阶段分配内存
//2 类内声明,类外初始化
//3 所有对象共享同一份数据

private:
static int m_B; //静态成员变量也是有访问权限的
};
int Person::m_A = 10;
int Person::m_B = 10;

void test01()
{
//静态成员变量两种访问方式

//1、通过对象
Person p1;
p1.m_A = 100;
cout << "p1.m_A = " << p1.m_A << endl;

Person p2;
p2.m_A = 200;
cout << "p1.m_A = " << p1.m_A << endl; //共享同一份数据
cout << "p2.m_A = " << p2.m_A << endl;

//2、通过类名
cout << "m_A = " << Person::m_A << endl;


//cout << "m_B = " << Person::m_B << endl; //私有权限访问不到
}

int main() {

test01();

system("pause");

return 0;
}

静态成员函数:

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
class Person
{

public:

//静态成员函数特点:
//1 程序共享一个函数
//2 静态成员函数只能访问静态成员变量

static void func()
{
cout << "func调用" << endl;
m_A = 100;
//m_B = 100; //错误,不可以访问非静态成员变量
}

static int m_A; //静态成员变量
int m_B; //
private:

//静态成员函数也是有访问权限的
static void func2()
{
cout << "func2调用" << endl;
}
};
int Person::m_A = 10;


void test01()
{
//静态成员变量两种访问方式

//1、通过对象
Person p1;
p1.func();

//2、通过类名
Person::func();


//Person::func2(); //私有权限访问不到
}

int main() {

test01();

system("pause");

return 0;
}

this 指针指向被调用的成员函数所属的对象

this 指针的用途

  • ==当形参和变量名相同时,可用 this 来进行区分,其指向类所有的成员变量==
  • ==在类的非静态成员函数中返回对象本身,可以使用 return *this==`
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
#include <iostream>

using namespace std;

class Person
{
public:
Person(int age) {
this->age = age;
}
Person& PersonAddPerson(Person p) {
this->age += p.age;
return *this;
}
public:
int age;
};

void test01() {
Person p1(10);
cout << "p1.age= " << p1.age << endl;

Person p2(10);
p2.PersonAddPerson(p1).PersonAddPerson(p1).PersonAddPerson(p1);
cout << "after add, the value of p2= " << p2.age << endl;
}

int main()
{
test01();

system("pause");

return 0;
}

空指针访问成员函数

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 Person {
public:

void ShowClassName() {
cout << "我是Person类!" << endl;
}

void ShowPerson() {
if (this == NULL) {
return;
}
cout << mAge << endl;
}

public:
int mAge;
};

void test01()
{
Person * p = NULL;
p->ShowClassName(); //空指针,可以调用成员函数
p->ShowPerson(); //但是如果成员函数中用到了this指针,就不可以了
}

int main() {

test01();

system("pause");

return 0;
}

const 修饰成员函数

常函数

按理说在设计类之前,就应该预先设置好一个函数是否为常函数。

  • 成员函数后加 const 即为常函数
  • 常函数内不可以进行修改成员属性
  • ==成员属性声明时加关键字 mutable 后,常函数中依然可以进行修改==

常对象

  • 声明对象前加 const 称该对象为常对象
  • 常对象只能调用常函数

image-20210302195757835

友元

友元的关键字为 ==friend==

同一个 class 生成的 object 互为友元。

友元的三种实现

  • 全局函数作友元

    重要的是,在类最上方使用 friend 关键字来声明全局函数(就像 C 中的函数需要提前声明一样)

1
2
3
4
5
6
7
8
9
10
11
12
class Building
{
//告诉编译器 goodGay全局函数 是 Building类的好朋友,可以访问类中的私有内容
friend void goodGay(Building * building);

public:

Building()
{
this->m_SittingRoom = "客厅";
this->m_BedRoom = "卧室";
}
1
2
3
4
5
6
7
8
9
10
11
12
public:
string m_SittingRoom; //客厅

private:
string m_BedRoom; //卧室
};

void goodGay(Building * building)
{
cout << "好基友正在访问: " << building->m_SittingRoom << endl;
cout << "好基友正在访问: " << building->m_BedRoom << endl;
}
1
2
3
4
5
6
7
8
9
10
11
12
void test01()
{
Building b;
goodGay(&b);
}

int main(){
test01();

system("pause");
return 0;
}
  • 类做友元
    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

    将一个类作为另一个类的友元,即可以访问类中的私有成员。

    ```c++
    class Building;
    class goodGay
    {
    public:

    goodGay();
    void visit();

    private:
    Building *building;
    };


    class Building
    {
    //告诉编译器 goodGay类是Building类的好朋友,可以访问到Building类中私有内容
    friend class goodGay;

    public:
    Building();

    public:
    string m_SittingRoom; //客厅
    private:
    string m_BedRoom;//卧室
    };

    // 在类外写成员函数
    Building::Building()
    {
    this->m_SittingRoom = "客厅";
    this->m_BedRoom = "卧室";
    }

    goodGay::goodGay()
    {
    building = new Building;
    }

    void goodGay::visit()
    {
    cout << "好基友正在访问" << building->m_SittingRoom << endl;
    cout << "好基友正在访问" << building->m_BedRoom << endl;
    }

    void test01()
    {
    goodGay gg;
    gg.visit();

    }

    int main(){

    test01();

    system("pause");
    return 0;
    }
  • 成员函数作友元

    重要点就在于:friend void goodGay::visit();

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13

    class Building;
    class goodGay
    {
    public:

    goodGay();
    void visit(); //只让visit函数作为Building的好朋友,可以发访问Building中私有内容
    void visit2();

    private:
    Building *building;
    };
  class Building
    {
        //告诉编译器  goodGay类中的visit成员函数 是Building好朋友,可以访问私有内容
        friend void goodGay::visit();

    public:
        Building();

    public:
        string m_SittingRoom; //客厅
    private:
        string m_BedRoom;//卧室
    };

    Building::Building()
    {
        this->m_SittingRoom = "客厅";
        this->m_BedRoom = "卧室";
    }

    goodGay::goodGay()
    {
        building = new Building;
    }

    void goodGay::visit()
    {
        cout << "好基友正在访问" << building->m_SittingRoom << endl;
        cout << "好基友正在访问" << building->m_BedRoom << endl;
    }

    void goodGay::visit2()
    {
        cout << "好基友正在访问" << building->m_SittingRoom << endl;
        //cout << "好基友正在访问" << building->m_BedRoom << endl;
    }

    void test01()
    {
        goodGay  gg;
        gg.visit();

    }

    int main(){
  test01();

  system("pause");
  return 0;
  }

友元总结:成为其他类或函数的朋友,就可以访问其私有成员。

操作符重载

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
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
class Person {
public:
Person() {};
Person(int a, int b)
{
this->m_A = a;
this->m_B = b;
}
//成员函数实现 + 号运算符重载
Person operator+(const Person& p) {
Person temp;
temp.m_A = this->m_A + p.m_A;
temp.m_B = this->m_B + p.m_B;
return temp;
}


public:
int m_A;
int m_B;
};

//全局函数实现 + 号运算符重载
//Person operator+(const Person& p1, const Person& p2) {
// Person temp(0, 0);
// temp.m_A = p1.m_A + p2.m_A;
// temp.m_B = p1.m_B + p2.m_B;
// return temp;
//}

//运算符重载 可以发生函数重载
Person operator+(const Person& p2, int val)
{
Person temp;
temp.m_A = p2.m_A + val;
temp.m_B = p2.m_B + val;
return temp;
}

void test() {

Person p1(10, 10);
Person p2(20, 20);

//成员函数方式
Person p3 = p2 + p1; //相当于 p2.operaor+(p1)
cout << "mA:" << p3.m_A << " mB:" << p3.m_B << endl;


Person p4 = p3 + 10; //相当于 operator+(p3,10)
cout << "mA:" << p4.m_A << " mB:" << p4.m_B << endl;

}

int main() {

test();

system("pause");

return 0;
}
  • 对于内置的数据类型的表达式的运算符不能被改变,如 int, double, float 但是自定义的是可以进行更改的。

左移运算符 << 重载

通常不会利用成员函数重载 << 运算符,因为无法实现 cout 在左侧

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
class Person {
friend ostream& operator<<(ostream& out, Person& p);

public:

Person(int a, int b)
{
this->m_A = a;
this->m_B = b;
}

//成员函数 实现不了 p << cout 不是我们想要的效果
//void operator<<(Person& p){
//}

private:
int m_A;
int m_B;
};

//全局函数实现左移重载
//ostream对象只能有一个
ostream& operator<<(ostream& cout, Person& p) {
cout << "a:" << p.m_A << " b:" << p.m_B;
return cout;
}

void test() {

Person p1(10, 20);

cout << p1 << "hello world" << endl; //链式编程
}

int main() {

test();

system("pause");

return 0;
}

递增运算符重载

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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
#include <iostream>
using namespace std;

class MyInteger {

friend ostream& operator<<(ostream& out, MyInteger myint);

public:
MyInteger() {
m_Num = 0;
}
//前置++
MyInteger& operator++() {
//先++
m_Num++;
//再返回
return *this;
}
//前置--
MyInteger& operator--() {
//先--
m_Num--;
//再返回
return *this;
}

//后置++
MyInteger operator++(int) {
//先返回
MyInteger temp = *this; //记录当前本身的值,然后让本身的值加1,但是返回的是以前的值,达到先返回后++;
m_Num++;
return temp;
}

//后置--
MyInteger operator--(int) {
//先返回
MyInteger temp = *this; //记录当前本身的值,然后让本身的值-1,但是返回的是以前的值,达到先返回后--;
m_Num--;
return temp;
}


private:
int m_Num;
};


ostream& operator<<(ostream& out, MyInteger myint) {
out << myint.m_Num;
return out;
}


//前置++ 先++ 再返回
void test01() {
MyInteger myInt;
cout << ++myInt << endl;
cout << myInt << endl;
}

//后置++ 先返回 再++
void test02() {

MyInteger myInt;
cout << myInt++ << endl;
cout << myInt << endl;
}

void test03() {
MyInteger myInt;
cout << myInt-- << endl;
}
void test04() {
MyInteger myInt;
cout << --myInt << endl;
}

int main() {

//test01();
//test02();
//test03();
test04();

system("pause");

return 0;
}

前置递增返回的是引用,后置递增返回的是值(由于提前 return 会结束当前函数,所以后置递增时,先用 temp 接受当前值,然后递增完,返回 temp 的值)

赋值运算符(=)的重载

C++ 编译器至少给一个类添加4个函数

  • 默认构造函数(无参数,函数体为空)
  • 默认析构函数(无参数,函数体为空)
  • 默认拷贝构造函数,对属性进行值拷贝
  • 赋值运算符 operator= ,对属性进行值拷贝

问题所在:

image-20210303223519840

解决方案:

image-20210303224140308

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
73
74
75
76
77
78
class Person
{
public:

Person(int age)
{
//将年龄数据开辟到堆区
m_Age = new int(age);
}

//重载赋值运算符
Person& operator=(Person &p)
{
if (m_Age != NULL)
{
delete m_Age;
m_Age = NULL;
}
//编译器默认提供的代码是浅拷贝
//m_Age = p.m_Age;

//提供深拷贝 解决浅拷贝的问题
m_Age = new int(*p.m_Age);

//返回自身
return *this;
}


~Person()
{
if (m_Age != NULL)
{
delete m_Age;
m_Age = NULL;
}
}

//年龄的指针
int *m_Age;

};


void test01()
{
Person p1(18);

Person p2(20);

Person p3(30);

p3 = p2 = p1; //赋值操作

cout << "p1的年龄为:" << *p1.m_Age << endl;

cout << "p2的年龄为:" << *p2.m_Age << endl;

cout << "p3的年龄为:" << *p3.m_Age << endl;
}

int main() {

test01();

//int a = 10;
//int b = 20;
//int c = 30;

//c = b = a;
//cout << "a = " << a << endl;
//cout << "b = " << b << endl;
//cout << "c = " << c << endl;

system("pause");

return 0;
}

关系运算符(==)重载

作用:重载关系运算符,可以让两个自定义类型对象进行对比操作

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
73
class Person
{
public:
Person(string name, int age)
{
this->m_Name = name;
this->m_Age = age;
};

bool operator==(Person & p)
{
if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
{
return true;
}
else
{
return false;
}
}

bool operator!=(Person & p)
{
if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
{
return false;
}
else
{
return true;
}
}

string m_Name;
int m_Age;
};

void test01()
{
//int a = 0;
//int b = 0;

Person a("孙悟空", 18);
Person b("孙悟空", 18);

if (a == b)
{
cout << "a和b相等" << endl;
}
else
{
cout << "a和b不相等" << endl;
}

if (a != b)
{
cout << "a和b不相等" << endl;
}
else
{
cout << "a和b相等" << endl;
}
}


int main() {

test01();

system("pause");

return 0;
}

函数调用运算符 () 重载

  • 函数调用运算符()也可以重载
  • 由于重载后使用的方式非常像函数调用,因此称为仿函数
  • 仿函数没有固定写法,非常灵活

即为对一个类的对象后使用()进行类似于函数式的调用

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
class MyPrint
{
public:
void operator()(string text)
{
cout << text << endl;
}

};
void test01()
{
//重载的()操作符 也称为仿函数
MyPrint myFunc;
myFunc("hello world");
}


class MyAdd
{
public:
int operator()(int v1, int v2)
{
return v1 + v2;
}
};

void test02()
{
MyAdd add;
int ret = add(10, 10);
cout << "ret = " << ret << endl;

//匿名对象调用
cout << "MyAdd()(100,100) = " << MyAdd()(100, 100) << endl;
}

int main() {

test01();
test02();

system("pause");

return 0;
}

继承

语法: class 子类 : 继承方式 父类

子类:派生类

父类:基类

权限:公共、保护、私有

image-20210304151734286

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
73
74
75
76
77
78
79
80
81
82
83
84
85
class Base1
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};

//公共继承
class Son1 :public Base1
{
public:
void func()
{
m_A; //可访问 public权限
m_B; //可访问 protected权限
//m_C; //不可访问
}
};

void myClass()
{
Son1 s1;
s1.m_A; //其他类只能访问到公共权限
}

//保护继承
class Base2
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
class Son2:protected Base2
{
public:
void func()
{
m_A; //可访问 protected权限
m_B; //可访问 protected权限
//m_C; //不可访问
}
};
void myClass2()
{
Son2 s;
//s.m_A; //不可访问
}

//私有继承
class Base3
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
class Son3:private Base3
{
public:
void func()
{
m_A; //可访问 private权限
m_B; //可访问 private权限
//m_C; //不可访问
}
};
class GrandSon3 :public Son3
{
public:
void func()
{
//Son3是私有继承,所以继承Son3的属性在GrandSon3中都无法访问到
//m_A;
//m_B;
//m_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
class Base
{
public:
int m_A;
protected:
int m_B;
private:
int m_C; //私有成员只是被隐藏了,但是还是会继承下去
};

//公共继承
class Son :public Base
{
public:
int m_D;
};

void test01()
{
cout << "sizeof Son = " << sizeof(Son) << endl;
}

int main() {

test01();

system("pause");

return 0;
}

父类中私有成员也是被子类继承下去了,只是由编译器给隐藏后访问不到

继承中构造和析构顺序

  • 构造顺序是先父后子
  • 析构顺序是先子后父

继承中同名成员的处理方式

  • 访问子类同名成员,直接访问
  • 访问父类同名成员,需要加作用域
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
class Base {
public:
Base()
{
m_A = 100;
}

void func()
{
cout << "Base - func()调用" << endl;
}

void func(int a)
{
cout << "Base - func(int a)调用" << endl;
}

public:
int m_A;
};


class Son : public Base {
public:
Son()
{
m_A = 200;
}

//当子类与父类拥有同名的成员函数,子类会隐藏父类中所有版本的同名成员函数
//如果想访问父类中被隐藏的同名成员函数,需要加父类的作用域
void func()
{
cout << "Son - func()调用" << endl;
}
public:
int m_A;
};

void test01()
{
Son s;

cout << "Son下的m_A = " << s.m_A << endl;
cout << "Base下的m_A = " << s.Base::m_A << endl;

s.func();
s.Base::func();
s.Base::func(10);

}
int main() {

test01();

system("pause");
return EXIT_SUCCESS;
}
//当子类与父类拥有同名的成员函数,子类会隐藏父类中所有版本的同名成员函数
//如果想访问父类中被隐藏的同名成员函数,需要加父类的作用域

总结:想要访问父类中被重复的对象,添加作用域才可访问到。

继承同名静态成员处理方式

  • 访问子类同名成员,直接访问即可
  • 访问父类同名成员,需要天机啊作用域
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
class Base {
public:
static void func()
{
cout << "Base - static void func()" << endl;
}
static void func(int a)
{
cout << "Base - static void func(int a)" << endl;
}

static int m_A;
};

int Base::m_A = 100;

class Son : public Base {
public:
static void func()
{
cout << "Son - static void func()" << endl;
}
static int m_A;
};

int Son::m_A = 200;

//同名成员属性
void test01()
{
//通过对象访问
cout << "通过对象访问: " << endl;
Son s;
cout << "Son 下 m_A = " << s.m_A << endl;
cout << "Base 下 m_A = " << s.Base::m_A << endl;

//通过类名访问
cout << "通过类名访问: " << endl;
cout << "Son 下 m_A = " << Son::m_A << endl;
cout << "Base 下 m_A = " << Son::Base::m_A << endl;
}

//同名成员函数
void test02()
{
//通过对象访问
cout << "通过对象访问: " << endl;
Son s;
s.func();
s.Base::func();

cout << "通过类名访问: " << endl;
Son::func();
Son::Base::func();
//出现同名,子类会隐藏掉父类中所有同名成员函数,需要加作作用域访问
Son::Base::func(100);
}
int main() {

//test01();
test02();

system("pause");

return 0;
}

多继承语法

C++ 允许一个类继承多个类

语法: class 子类继承方式 父类1继承方式 父类2

多继承可能会引发父类中有同名成员出现,需要加作用域区分

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
class Base1 {
public:
Base1()
{
m_A = 100;
}
public:
int m_A;
};

class Base2 {
public:
Base2()
{
m_A = 200; //开始是m_B 不会出问题,但是改为mA就会出现不明确
}
public:
int m_A;
};

//语法:class 子类:继承方式 父类1 ,继承方式 父类2
class Son : public Base2, public Base1
{
public:
Son()
{
m_C = 300;
m_D = 400;
}
public:
int m_C;
int m_D;
};


//多继承容易产生成员同名的情况
//通过使用类名作用域可以区分调用哪一个基类的成员
void test01()
{
Son s;
cout << "sizeof Son = " << sizeof(s) << endl;
cout << s.Base1::m_A << endl;
cout << s.Base2::m_A << endl;
}

int main() {

test01();

system("pause");

return 0;
}

菱形继承问题的解决方式

菱形继承即,多继承了父类的相同同名属性,带来的主要问题是子类继承两份相同的数据,导致资源浪费以及毫无意义

在继承之前,添加关键字 virtual 变为虚继承

class Sheep : virtual public Animal{};

image-20210305004227323

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
class Animal
{
public:
int m_Age;
};

//继承前加virtual关键字后,变为虚继承
//此时公共的父类Animal称为虚基类
class Sheep : virtual public Animal {};
class Tuo : virtual public Animal {};
class SheepTuo : public Sheep, public Tuo {};

void test01()
{
SheepTuo st;
st.Sheep::m_Age = 100;
st.Tuo::m_Age = 200;

cout << "st.Sheep::m_Age = " << st.Sheep::m_Age << endl;
cout << "st.Tuo::m_Age = " << st.Tuo::m_Age << endl;
cout << "st.m_Age = " << st.m_Age << endl;
}


int main() {

test01();

system("pause");

return 0;
}

多态

  • 静态多态: 函数重载 和 运算符重载属于静态多态,复用函数名

    函数地址在编译阶段就能确定,静态联编

Screen Shot 2021-03-05 at 15.07.53

  • ==动态多态: 派生类和虚函数实现运行时多态==

    函数地址在运行阶段才能确定,就是动态联编

静态多态和动态多态区别:

  • 静态多态的函数地址早绑定 - 编译阶段确定函数地址

  • 动态多态的函数地址晚绑定 - 运行阶段确定函数地址

    (动态地址通过在父类函数中使用 virtual 来进行虚函数)

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
class Animal
{
public:
//Speak函数就是虚函数
//函数前面加上virtual关键字,变成虚函数,那么编译器在编译的时候就不能确定函数调用了。
virtual void speak()
{
cout << "动物在说话" << endl;
}
};

class Cat :public Animal
{
public:
void speak()
{
cout << "小猫在说话" << endl;
}
};

class Dog :public Animal
{
public:

void speak()
{
cout << "小狗在说话" << endl;
}

};
//我们希望传入什么对象,那么就调用什么对象的函数
//如果函数地址在编译阶段就能确定,那么静态联编
//如果函数地址在运行阶段才能确定,就是动态联编

void DoSpeak(Animal & animal)
{
animal.speak();
}
//
//多态满足条件:
//1、有继承关系
//2、子类重写父类中的虚函数
//多态使用:
//父类指针或引用指向子类对象

void test01()
{
Cat cat;
DoSpeak(cat);


Dog dog;
DoSpeak(dog);
}


int main() {

test01();

system("pause");

return 0;
}

纯虚函数和抽象类

父类中的虚函数实现大多数时候是毫无意义的,通常都是利用子类中进行调用子类的重写的内容。

子类必须重写父类中的纯虚函数(Java接口同)

当类中有了纯虚函数,那么这个类就称为==抽象类==

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
class Base
{
public:
//纯虚函数
//类中只要有一个纯虚函数就称为抽象类
//抽象类无法实例化对象
//子类必须重写父类中的纯虚函数,否则也属于抽象类
virtual void func() = 0;
};

class Son :public Base
{
public:
virtual void func()
{
cout << "func调用" << endl;
};
};

void test01()
{
Base * base = NULL;
//base = new Base; // 错误,抽象类无法实例化对象
base = new Son;
base->func();
delete base;//记得销毁
}

int main() {

test01();

system("pause");

return 0;
}

多态案例,制作饮品

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
73
74
75
76
77
78
79
80
81
82
#include <iostream>
using namespace std;

class Abstractdrinking{
public:
virtual void HeatWater() = 0;
virtual void Brew() = 0;
virtual void PourIn() = 0;
virtual void AddSup() = 0;

void MakeDrinking(){
HeatWater();
Brew();
PourIn();
AddSup();
}
};
class Coffee: public Abstractdrinking{
virtual void HeatWater(){
cout<<"Heat NongF Water."<<endl;
}
virtual void Brew(){
cout<<"brew coffee."<<endl;
}
virtual void PourIn(){
cout<<"Pour in NongF Water for Coffee."<<endl;
}
virtual void AddSup(){
cout<<"Add support milk & sugar."<<endl;
}

};
class Tea: public Abstractdrinking{
virtual void HeatWater(){
cout<<"Heat Quan Water."<<endl;
}
virtual void Brew(){
cout<<"brew tea."<<endl;
}
virtual void PourIn(){
cout<<"Pour in Quan Water for tea."<<endl;
}
virtual void AddSup(){
cout<<"Add support lemon."<<endl;
}

};

void doWork(Abstractdrinking &abstractdrinking){
abstractdrinking.MakeDrinking();
}
void doWork(Abstractdrinking *abstractdrinking){
abstractdrinking->MakeDrinking();
delete abstractdrinking;
abstractdrinking = nullptr;
}
void test01(){
Abstractdrinking *coffee = new Coffee();
doWork(*coffee);
delete coffee;
coffee = nullptr;
cout<<"---------------------------------"<<endl;
Abstractdrinking *tea = new Tea();
doWork(*tea);
delete tea;
tea = nullptr;
}
void test02(){
cout<<"doWork with test02 using point."<<endl;
doWork(new Coffee());
cout<<"---------------------------------"<<endl;
doWork(new Tea());

}

int main() {
test01();
cout<<"================================="<<endl;
test02();

return 0;
}

image-20210305181452990

虚析构和纯虚析构

父类指针在析构的时候,不会调用子类中析构函数,导致子类如果有堆区属性,会出现内存泄漏的情况

利用虚析构可以解决父类指针释放子类对象时不干净的问题

virtual ~Animal(){cout<<"destruct"<<endl;}

==纯虚析构需要声明也需要实现==

虚析构和纯虚析构共性:

  • 可以解决父类指针释放子类对象
  • 都需要有具体的函数实现

虚析构和纯虚析构区别:

  • 如果是纯虚析构,该类属于抽象类,无法实例化对象

虚析构语法:

virtual ~类名(){}

纯虚析构语法:

virtual ~类名()=0

类名::~类名(){} (类内声明,类外实现)

总结:

​ 1. 虚析构或纯虚析构就是用来解决通过父类指针释放子类对象

​ 2. 如果子类中没有堆区数据,可以不写为虚析构或纯虚析构

3. 拥有纯虚析构函数的类也属于抽象类(抽象类无法实例化对象)

多态案例:电脑组装

案例描述:

电脑主要组成部件为 CPU(用于计算),显卡(用于显示),内存条(用于存储)

将每个零件封装出抽象基类,并且提供不同的厂商生产不同的零件,例如Intel厂商和Lenovo厂商

创建电脑类提供让电脑工作的函数,并且调用每个零件工作的接口

测试时组装三台不同的电脑进行工作

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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
#include<iostream>
using namespace std;

//抽象CPU类
class CPU
{
public:
//抽象的计算函数
virtual void calculate() = 0;
};

//抽象显卡类
class VideoCard
{
public:
//抽象的显示函数
virtual void display() = 0;
};

//抽象内存条类
class Memory
{
public:
//抽象的存储函数
virtual void storage() = 0;
};

//电脑类
class Computer
{
public:
Computer(CPU * cpu, VideoCard * vc, Memory * mem)
{
m_cpu = cpu;
m_vc = vc;
m_mem = mem;
}

//提供工作的函数
void work()
{
//让零件工作起来,调用接口
m_cpu->calculate();

m_vc->display();

m_mem->storage();
}

//提供析构函数 释放3个电脑零件
~Computer()
{

//释放CPU零件
if (m_cpu != NULL)
{
delete m_cpu;
m_cpu = NULL;
}

//释放显卡零件
if (m_vc != NULL)
{
delete m_vc;
m_vc = NULL;
}

//释放内存条零件
if (m_mem != NULL)
{
delete m_mem;
m_mem = NULL;
}
}

private:

CPU * m_cpu; //CPU的零件指针
VideoCard * m_vc; //显卡零件指针
Memory * m_mem; //内存条零件指针
};

//具体厂商
//Intel厂商
class IntelCPU :public CPU
{
public:
virtual void calculate()
{
cout << "Intel的CPU开始计算了!" << endl;
}
};

class IntelVideoCard :public VideoCard
{
public:
virtual void display()
{
cout << "Intel的显卡开始显示了!" << endl;
}
};

class IntelMemory :public Memory
{
public:
virtual void storage()
{
cout << "Intel的内存条开始存储了!" << endl;
}
};

//Lenovo厂商
class LenovoCPU :public CPU
{
public:
virtual void calculate()
{
cout << "Lenovo的CPU开始计算了!" << endl;
}
};

class LenovoVideoCard :public VideoCard
{
public:
virtual void display()
{
cout << "Lenovo的显卡开始显示了!" << endl;
}
};

class LenovoMemory :public Memory
{
public:
virtual void storage()
{
cout << "Lenovo的内存条开始存储了!" << endl;
}
};


void test01()
{
//第一台电脑零件
CPU * intelCpu = new IntelCPU;
VideoCard * intelCard = new IntelVideoCard;
Memory * intelMem = new IntelMemory;

cout << "第一台电脑开始工作:" << endl;
//创建第一台电脑
Computer * computer1 = new Computer(intelCpu, intelCard, intelMem);
computer1->work();
delete computer1;

cout << "-----------------------" << endl;
cout << "第二台电脑开始工作:" << endl;
//第二台电脑组装
Computer * computer2 = new Computer(new LenovoCPU, new LenovoVideoCard, new LenovoMemory);;
computer2->work();
delete computer2;

cout << "-----------------------" << endl;
cout << "第三台电脑开始工作:" << endl;
//第三台电脑组装
Computer * computer3 = new Computer(new LenovoCPU, new IntelVideoCard, new LenovoMemory);;
computer3->work();
delete computer3;

}

文件操作

文件类型:

  • 文本文件 - 以文本的 ASCII 码形式存储在计算机中
  • 以二进制形式存储在计算机中,一般用户不能直接读懂

==包含头文件 #include\==

操作文件的三大类:

  • ofstream:写操作
  • ifstream:读操作
  • fstream:读写操作
打开方式 解释
ios::in 为读文件而打开文件
ios::out 为写文件而打开文件
ios::ate 初始位置:文件尾
ios::app 追加方式写文件
ios::trunc 如果文件存在先删除,再创建
ios::binary 二进制方式

利用 | 操作符可以设置二进制方式写文件 ios::binary | ios::out

写文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <fstream>

void test01()
{
ofstream ofs;
ofs.open("test.txt", ios::out);

ofs << "姓名:张三" << endl;
ofs << "性别:男" << endl;
ofs << "年龄:18" << endl;

ofs.close();
}

int main() {

test01();

system("pause");

return 0;
}
  • 文件操作必须包含头文件 fstream
  • 读文件可以利用 ofstream ,或者fstream类
  • 打开文件时候需要指定操作文件的路径,以及打开方式
  • 利用<<可以向文件中写数据
  • 操作完毕,要关闭文件

读文件

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
#include <fstream>
#include <string>
void test01()
{
ifstream ifs;
ifs.open("test.txt", ios::in);

if (!ifs.is_open())
{
cout << "文件打开失败" << endl;
return;
}

//第一种方式
//char buf[1024] = { 0 };
//while (ifs >> buf)
//{
// cout << buf << endl;
//}

//第二种
//char buf[1024] = { 0 };
//while (ifs.getline(buf,sizeof(buf)))
//{
// cout << buf << endl;
//}

//第三种
//string buf;
//while (getline(ifs, buf))
//{
// cout << buf << endl;
//}

//第四种
//char c;
//while( (c = ifs.get()) != EOF )
//{
// cout << c;
//}

char c;
while ((c = ifs.get()) != EOF)
{
cout << c;
}

ifs.close();


}

int main() {

test01();

system("pause");

return 0;
}

is_open 可以判断文件是否打开成功

二进制文件

写文件

函数原型:

ostream& write(const char * buffer, int len);

buffer 指向内存中一段存储空间,len 是读写的字节数

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
#include <fstream>
#include <string>

class Person
{
public:
char m_Name[64];
int m_Age;
};

//二进制文件 写文件
void test01()
{
//1、包含头文件

//2、创建输出流对象
ofstream ofs("person.txt", ios::out | ios::binary);

//3、打开文件
//ofs.open("person.txt", ios::out | ios::binary);

Person p = {"张三" , 18};

//4、写文件
// (const char *)&p 的原因是 *&p 取出来的是 *p 类型,需要转换为 char * 类型
ofs.write((const char *)&p, sizeof(p));

//5、关闭文件
ofs.close();
}

int main() {

test01();

system("pause");

return 0;
}

读文件

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
#include <fstream>
#include <string>

class Person
{
public:
char m_Name[64];
int m_Age;
};

void test01()
{
ifstream ifs("person.txt", ios::in | ios::binary);
if (!ifs.is_open())
{
cout << "文件打开失败" << endl;
}

Person p;
ifs.read((char *)&p, sizeof(p));

cout << "姓名: " << p.m_Name << " 年龄: " << p.m_Age << endl;
}

int main() {

test01();

system("pause");

return 0;
}

C++ 泛型概念(模板)

模板特点:

  • 模板不可以直接使用,它作为一个框架

  • 模板不是万能通用的

函数模板和类模板

函数模板语法

函数模板作用:

建立一个通用函数,其函数返回值类型和形参类型可以不具体制定,用一个虚拟的类型来代表。

语法:

1
2
template<typename T>
函数声明或定义

解释:

template —- 声明创建模板

typename —- 表面其后面的符号是一种数据类型,可以用class代替(即为类模板)

T —- 通用的数据类型,名称可以替换,通常为大写字母

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

//交换整型函数
void swapInt(int& a, int& b) {
int temp = a;
a = b;
b = temp;
}

//交换浮点型函数
void swapDouble(double& a, double& b) {
double temp = a;
a = b;
b = temp;
}

//利用模板提供通用的交换函数
template<typename T>
void mySwap(T& a, T& b)
{
T temp = a;
a = b;
b = temp;
}

void test01()
{
int a = 10;
int b = 20;

//swapInt(a, b);

//利用模板实现交换
//1、自动类型推导
mySwap(a, b);

//2、显示指定类型
mySwap<int>(a, b);

cout << "a = " << a << endl;
cout << "b = " << b << endl;

}

int main() {

test01();

system("pause");

return 0;
}

总结:

  • 函数模板有两种使用方式:自动类型推导、显示指定类型

  • 模板的目的是为了提高复用性,将类型参数化

函数模板注意事项

注意事项:

  • 自动类型推导,必须推导出一致的数据类型T,才可以使用
  • 模板必须要确定出T的数据类型,才可以使用
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
//利用模板提供通用的交换函数
template<class T>
void mySwap(T& a, T& b)
{
T temp = a;
a = b;
b = temp;
}


// 1、自动类型推导,必须推导出一致的数据类型T,才可以使用
void test01()
{
int a = 10;
int b = 20;
char c = 'c';

mySwap(a, b); // 正确,可以推导出一致的T
//mySwap(a, c); // 错误,推导不出一致的T类型
}


// 2、模板必须要确定出T的数据类型,才可以使用
template<class T>
void func()
{
cout << "func 调用" << endl;
}

void test02()
{
//func(); //错误,模板不能独立使用,必须确定出T的类型
func<int>(); //利用显示指定类型的方式,给T一个类型,才可以使用该模板
}

int main() {

test01();
test02();

system("pause");

return 0;
}

使用模板时必须确定出通用数据类型 T ,并且能够推导出一致的类型

函数模板案例:选择排序

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
template<typename T>
void mySwap(T &a, T &b)
{
T temp = a;
a = b;
b = temp;
}

template<class T>
void mySort(T arr[], T len)
{
for(int i = 0; i < len; i++)
{
int max = i;
for(int j = i+1; j < len; j++){
if(arr[j] > max){
max = j;
}
}
if(max != i){
mySwap(arr[max], arr[i]);
}
}
}

template<typename T>
void printArray(T arr[], int len) {

for (int i = 0; i < len; i++) {
cout << arr[i] << " ";
}
cout << endl;
}
void test01()
{
//测试char数组
char charArr[] = "bdcfeagh";
//int num = sizeof(charArr) / sizeof(char);
//mySort(charArr, num);
mySort(charArr, sizeof(charArr)/sizeof(char) );
//printArray(charArr, num);
printArray(charArr, sizeof(charArr)/sizeof(char));
}

void test02()
{
//测试int数组
int intArr[] = { 7, 5, 8, 1, 3, 9, 2, 4, 6 };
int num = sizeof(intArr) / sizeof(int);
mySort(intArr, num);
printArray(intArr, num);
}

int main() {

test01();
test02();

//system("pause");

return 0;
}

普通函数与函数模板的区别

普通函数与函数模板区别:

  • 普通函数调用时可以发生自动类型转换(隐式类型转换)
  • 函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换
  • 如果利用显示指定类型的方式,可以发生隐式类型转换
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
//普通函数
int myAdd01(int a, int b)
{
return a + b;
}

//函数模板
template<class T>
T myAdd02(T a, T b)
{
return a + b;
}

//使用函数模板时,如果用自动类型推导,不会发生自动类型转换,即隐式类型转换
void test01()
{
int a = 10;
int b = 20;
char c = 'c';

cout << myAdd01(a, c) << endl; //正确,将char类型的'c'隐式转换为int类型 'c' 对应 ASCII码 99

//myAdd02(a, c); // 报错,使用自动类型推导时,不会发生隐式类型转换

myAdd02<int>(a, c); //正确,如果用显示指定类型,可以发生隐式类型转换
}

int main() {

test01();

system("pause");

return 0;
}

模板的局限性

局限性在于:对于模板中的 T 这个数据类型来说,有些时候它推导不出来 T 属于什么类型 ,这是最大的局限性

例如:

1
2
3
4
5
template<class T>
void f(T a, T b)
{
a = b;
}

在上述代码中提供的赋值操作,如果传入的a和b是一个数组,就无法实现了

再例如:

1
2
3
4
5
template<class T>
void f(T a, T b)
{
if(a > b) { ... }
}

在上述代码中,如果T的数据类型传入的是像Person这样的自定义数据类型,也无法正常运行

因此C++为了解决这种问题,提供模板的重载,可以为这些特定的类型提供具体化的模板

具体化的模板

语法:

1
2
3
//具体化,显示具体化的原型和定意思以template<>开头,并通过名称来指出类型
//具体化优先于常规模板
template<> bool myCompare(Person &p1, Person &p2)

对比于:

1
2
3
4
template <class T>
void myTest(int a, int b){
...
}

类模板

注意:

  • 类模板只能用显示指定类型方式
  • 类模板中模板参数列表可以有默认参数

语法:

1
2
template<typename T>
class className{};

在类模板中,声明模板 template 后面加类

类模板与函数模板区别

  • 类模板没有自动类型推导的使用方式
  • 类模板使用只能用显示指定类型方式
  • 类模板在模板参数列表中有默认参数
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
#include <string>
//类模板
template<class NameType, class AgeType = int> //设置的默认参数 int
class Person
{
public:
Person(NameType name, AgeType age)
{
this->mName = name;
this->mAge = age;
}
void showPerson()
{
cout << "name: " << this->mName << " age: " << this->mAge << endl;
}
public:
NameType mName;
AgeType mAge;
};

//1、类模板没有自动类型推导的使用方式
void test01()
{
// Person p("孙悟空", 1000); // 错误 类模板使用时候,不可以用自动类型推导
Person <string ,int>p("孙悟空", 1000); //必须使用显示指定类型的方式,使用类模板
p.showPerson();
}

//2、类模板在模板参数列表中可以有默认参数
void test02()
{
Person <string> p("猪八戒", 999); //类模板中的模板参数列表 可以指定默认参数
p.showPerson();
}

int main() {

test01();

test02();

system("pause");

return 0;
}

类模板中成员函数创建时机

  • 普通类中的成员函数一开始就可以创建
  • 类模板中的成员函数在调用时才创建
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
class Person1
{
public:
void showPerson1()
{
cout << "Person1 show" << endl;
}
};

class Person2
{
public:
void showPerson2()
{
cout << "Person2 show" << endl;
}
};

template<class T>
class MyClass
{
public:
T obj;

//类模板中的成员函数,并不是一开始就创建的,而是在模板调用时再生成

void fun1() { obj.showPerson1(); }
void fun2() { obj.showPerson2(); }

};

void test01()
{
MyClass<Person1> m;

m.fun1();

//m.fun2();//编译会出错,说明函数调用才会去创建成员函数
}

int main() {

test01();

system("pause");

return 0;
}

类模板对象作函数参数

  1. 指定传入的类型 —- 直接显示对象的数据类型
  2. 参数模板化 —- 将对象中的参数变为模板进行传递
  3. 整个类模板化 —- 将这个对象类型 模板化进行传递

其中指定数据类型是使用最广泛的

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
#include <string>
//类模板
template<class NameType, class AgeType = int>
class Person
{
public:
Person(NameType name, AgeType age)
{
this->mName = name;
this->mAge = age;
}
void showPerson()
{
cout << "name: " << this->mName << " age: " << this->mAge << endl;
}
public:
NameType mName;
AgeType mAge;
};

//1、指定传入的类型
void printPerson1(Person<string, int> &p)
{
p.showPerson();
}
void test01()
{
Person <string, int >p("孙悟空", 100);
printPerson1(p);
}

//2、参数模板化
template <class T1, class T2>
void printPerson2(Person<T1, T2>&p)
{
p.showPerson();
cout << "T1的类型为: " << typeid(T1).name() << endl;
cout << "T2的类型为: " << typeid(T2).name() << endl;
}
void test02()
{
Person <string, int >p("猪八戒", 90);
printPerson2(p);
}

//3、整个类模板化
template<class T>
void printPerson3(T & p)
{
cout << "T的类型为: " << typeid(T).name() << endl;
p.showPerson();

}
void test03()
{
Person <string, int >p("唐僧", 30);
printPerson3(p);
}

int main() {

test01();
test02();
test03();

system("pause");

return 0;
}

类模板与继承

  • 当子类继承的父类是一个类模板时,子类在声明的时候,要指定出父类中 T 的类型
  • 如果不指定,编译器无法给子类分配内存
  • 如果想灵活指定出父类中T的类型,子类也需变为类模板
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
template<class T>
class Base
{
T m;
};

//class Son:public Base //错误,c++编译需要给子类分配内存,必须知道父类中T的类型才可以向下继承
class Son :public Base<int> //必须指定一个类型
{
};
void test01()
{
Son c;
}

//类模板继承类模板 ,可以用T2指定父类中的T类型
template<class T1, class T2>
class Son2 :public Base<T2>
{
public:
Son2()
{
cout << typeid(T1).name() << endl;
cout << typeid(T2).name() << endl;
}
};

void test02()
{
Son2<int, char> child1;
}

int main() {

test01();

test02();

system("pause");

return 0;
}

类模板成员函数与类外实现

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
#include <string>

//类模板中成员函数类外实现
template<class T1, class T2>
class Person {
public:
//成员函数类内声明
Person(T1 name, T2 age);
void showPerson();

public:
T1 m_Name;
T2 m_Age;
};

//构造函数 类外实现
template<class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age) {
this->m_Name = name;
this->m_Age = age;
}

//成员函数 类外实现
template<class T1, class T2>
void Person<T1, T2>::showPerson() {
cout << "姓名: " << this->m_Name << " 年龄:" << this->m_Age << endl;
}

void test01()
{
Person<string, int> p("Tom", 20);
p.showPerson();
}

int main() {

test01();

system("pause");

return 0;
}

类外实现需要添加模板参数列表

类模板分文件编写

#pragma once防止头文件重复编译,一般写在.h头文件中

问题:

  • 类模板中成员函数创建时机是在调用阶段,导致分文件编写时链接不到

解决:

  • 解决方式1:直接包含在.cpp源文件
  • 解决方式2:只将类模板的声明和实现写到同一个文件中,并更改后缀名为.hpp,hpp是约定的名称,并不是强制

person.hpp

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
#pragma once
#include <iostream>
using namespace std;
#include <string>

template<class T1, class T2>
class Person {
public:
Person(T1 name, T2 age);
void showPerson();
public:
T1 m_Name;
T2 m_Age;
};

//构造函数 类外实现
template<class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age) {
this->m_Name = name;
this->m_Age = age;
}

//成员函数 类外实现
template<class T1, class T2>
void Person<T1, T2>::showPerson() {
cout << "姓名: " << this->m_Name << " 年龄:" << this->m_Age << endl;
}

main.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include<iostream>
using namespace std;

//#include "person.h"
#include "person.cpp" //解决方式1,包含cpp源文件

//解决方式2,将声明和实现写到一起,文件后缀名改为.hpp
#include "person.hpp"
void test01()
{
Person<string, int> p("Tom", 10);
p.showPerson();
}

int main() {

test01();

system("pause");

return 0;
}

类模板案例

  • 可以对内置数据类型以及自定义数据类型的数据进行存储
  • 将数组中的数据存储到堆区
  • 构造函数中可以传入数组的容量
  • 提供对应的拷贝构造函数以及operator=防止浅拷贝问题
  • 提供尾插法和尾删法对数组中的数据进行增加和删除
  • 可以通过下标的方式访问数组中的元素
  • 可以获取数组中当前元素个数和数组的容量

myArr.hpp

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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
#pragma once
#include <iostream>
using namespace std;

template<class T>
class MyArray
{
public:

//构造函数
MyArray(int capacity)
{
this->m_Capacity = capacity;
this->m_Size = 0;
pAddress = new T[this->m_Capacity];
}

//拷贝构造
MyArray(const MyArray & arr)
{
this->m_Capacity = arr.m_Capacity;
this->m_Size = arr.m_Size;
this->pAddress = new T[this->m_Capacity];
for (int i = 0; i < this->m_Size; i++)
{
//如果T为对象,而且还包含指针,必须需要重载 = 操作符,因为这个等号不是 构造 而是赋值,
// 普通类型可以直接= 但是指针类型需要深拷贝
this->pAddress[i] = arr.pAddress[i];
}
}

//重载= 操作符 防止浅拷贝问题
MyArray& operator=(const MyArray& myarray) {

if (this->pAddress != NULL) {
delete[] this->pAddress;
this->m_Capacity = 0;
this->m_Size = 0;
}

this->m_Capacity = myarray.m_Capacity;
this->m_Size = myarray.m_Size;
this->pAddress = new T[this->m_Capacity];
for (int i = 0; i < this->m_Size; i++) {
this->pAddress[i] = myarray[i];
}
return *this;
}

//重载[] 操作符 arr[0]
T& operator [](int index)
{
return this->pAddress[index]; //不考虑越界,用户自己去处理
}

//尾插法
void Push_back(const T & val)
{
if (this->m_Capacity == this->m_Size)
{
return;
}
this->pAddress[this->m_Size] = val;
this->m_Size++;
}

//尾删法
void Pop_back()
{
if (this->m_Size == 0)
{
return;
}
this->m_Size--;
}

//获取数组容量
int getCapacity()
{
return this->m_Capacity;
}

//获取数组大小
int getSize()
{
return this->m_Size;
}


//析构
~MyArray()
{
if (this->pAddress != NULL)
{
delete[] this->pAddress;
this->pAddress = NULL;
this->m_Capacity = 0;
this->m_Size = 0;
}
}

private:
T * pAddress; //指向一个堆空间,这个空间存储真正的数据
int m_Capacity; //容量
int m_Size; // 大小
};

main.cpp

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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
#include "myArr.hpp"
#include <string>

void printIntArray(MyArray<int>& arr) {
for (int i = 0; i < arr.getSize(); i++) {
cout << arr[i] << " ";
}
cout << endl;
}

//测试内置数据类型
void test01()
{
MyArray<int> array1(10);
for (int i = 0; i < 10; i++)
{
array1.Push_back(i);
}
cout << "array1打印输出:" << endl;
printIntArray(array1);
cout << "array1的大小:" << array1.getSize() << endl;
cout << "array1的容量:" << array1.getCapacity() << endl;

cout << "--------------------------" << endl;

MyArray<int> array2(array1);
array2.Pop_back();
cout << "array2打印输出:" << endl;
printIntArray(array2);
cout << "array2的大小:" << array2.getSize() << endl;
cout << "array2的容量:" << array2.getCapacity() << endl;
}

//测试自定义数据类型
class Person {
public:
Person() {}
Person(string name, int age) {
this->m_Name = name;
this->m_Age = age;
}
public:
string m_Name;
int m_Age;
};

void printPersonArray(MyArray<Person>& personArr)
{
for (int i = 0; i < personArr.getSize(); i++) {
cout << "姓名:" << personArr[i].m_Name << " 年龄: " << personArr[i].m_Age << endl;
}

}

void test02()
{
//创建数组
MyArray<Person> pArray(10);
Person p1("孙悟空", 30);
Person p2("韩信", 20);
Person p3("妲己", 18);
Person p4("王昭君", 15);
Person p5("赵云", 24);

//插入数据
pArray.Push_back(p1);
pArray.Push_back(p2);
pArray.Push_back(p3);
pArray.Push_back(p4);
pArray.Push_back(p5);

printPersonArray(pArray);

cout << "pArray的大小:" << pArray.getSize() << endl;
cout << "pArray的容量:" << pArray.getCapacity() << endl;

}

int main() {

//test01();

test02();

system("pause");

return 0;
}

STL

  • 标准模板库
  • 容器container、算法algorithm、迭代器iterator

  • 容器和算法之间通过迭代器进行无缝连接

  • STL 几乎所有代码都采用了模板类或模板函数

STL大体分为六大组件,分别是:容器、算法、迭代器、仿函数、适配器(配接器)、空间配置器

  1. 容器:各种数据结构,如vector、list、deque、set、map等,用来存放数据。
  2. 算法:各种常用的算法,如sort、find、copy、for_each等
  3. 迭代器:扮演了容器与算法之间的胶合剂。
  4. 仿函数:行为类似函数,可作为算法的某种策略。
  5. 适配器:一种用来修饰容器或者仿函数或迭代器接口的东西。
  6. 空间配置器:负责空间的配置与管理。
  • 容器:置物之所也,常用数据结构:数组,链表,树,栈,队列,集合,映射表

    序列式容器:强调值的排序,序列式容器中的每个元素均有固定的位置。

    **关联式容器**:二叉树结构,各元素之间没有严格的物理上的顺序关系
    
  • 算法:质变算法:是指运算过程中会更改区间内的元素的内容。例如拷贝,替换,删除等等

    非质变算法:是指运算过程中不会更改区间内的元素内容,例如查找、计数、遍历、寻找极值等等

  • 迭代器:提供一种方法,使之能够依序寻访某个容器所含的各个元素,而又无需暴露该容器的内部表示方式。

vector

通过迭代器访问容器 vector 中的数据

vector<int>::iterator itBegin = v.begin(); 起始迭代器,指向容器中第一个元素

vector<int>::iterator itBegin = v.end(); 结束迭代器,指向容器中最后一个元素的下一个位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
vector<int> v;
v.push_back(10);
v.push_back(11);
v.push_back(12);

vector<int>::iterator itBegin = v.begin();
vector<int>::iterator itEnd = v.end();
while(itBegin != itEnd){
cout << *itBegin++<<endl;
}

//for circle
for(vector<int>::iterator it = v.begin(); it != v.end; it++){
cout<<*it<<endl;
}

//for-each
// 首先需要包含头文件 #include <algorithm>
void myPrint(int val){
cout<< val <<endl;
}
for_each(v.begin(), v.end(), myPrint);

vector容器中存放自定义数据类型

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
73
74
#include <vector>
#include <string>

//自定义数据类型
class Person {
public:
Person(string name, int age) {
mName = name;
mAge = age;
}
public:
string mName;
int mAge;
};
//存放对象
void test01() {

vector<Person> v;

//创建数据
Person p1("aaa", 10);
Person p2("bbb", 20);
Person p3("ccc", 30);
Person p4("ddd", 40);
Person p5("eee", 50);

v.push_back(p1);
v.push_back(p2);
v.push_back(p3);
v.push_back(p4);
v.push_back(p5);

for (vector<Person>::iterator it = v.begin(); it != v.end(); it++) {
cout << "Name:" << (*it).mName << " Age:" << (*it).mAge << endl;

}
}


//放对象指针
void test02() {

vector<Person*> v;

//创建数据
Person p1("aaa", 10);
Person p2("bbb", 20);
Person p3("ccc", 30);
Person p4("ddd", 40);
Person p5("eee", 50);

v.push_back(&p1);
v.push_back(&p2);
v.push_back(&p3);
v.push_back(&p4);
v.push_back(&p5);

for (vector<Person*>::iterator it = v.begin(); it != v.end(); it++) {
Person * p = (*it);
cout << "Name:" << p->mName << " Age:" << (*it)->mAge << endl;
}
}


int main() {

test01();

test02();

system("pause");

return 0;
}

vector 容器嵌套容器

1
2
3
4
5
6
7
8
9
10
11
12
13

vector<vector<int>> v;

vector<int> v1;
vector<int> v2;
vector<int> v3;
vector<int> v4;

for( vector<vector<int>>::iterator it = v.begin(); it != v.end(); it++ ){
for(vector<int>::iterator vit = (*it).begin(); vit != (*it).end(); (*it)++ ){
cout<< *vit <<endl;
}
}

插入和删除

功能描述:

  • 对vector容器进行插入、删除操作

函数原型:

  • push_back(ele); //尾部插入元素ele
  • pop_back(); //删除最后一个元素
  • insert(const_iterator pos, ele); //迭代器指向位置pos插入元素ele
  • insert(const_iterator pos, int count,ele);//迭代器指向位置pos插入count个元素ele
  • erase(const_iterator pos); //删除迭代器指向的元素
  • erase(const_iterator start, const_iterator end);//删除迭代器从start到end之间的元素
  • clear(); //删除容器中所有元素

swap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
#include <vector>
#include<algorithm>
using namespace std;

void myPrint(int val){
cout<< val <<endl;
}
int main(){
vector<int> v1 = {1,2,3,4,5};
vector<int> v2 = {6,7,8,9,10};
for_each(v1.begin(), v1.end(), myPrint );
for_each(v2.begin(), v2.end(), myPrint );
v1.swap(v2);
for_each(v1.begin(), v1.end(), myPrint );
for_each(v2.begin(), v2.end(), myPrint );

return 0;
}

==swap 可以用来进行一个收缩内存的效果:vector\(v).swap(v);==

利用 reserve 预留空间

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
#include <vector>

void test01()
{
vector<int> v;

//预留空间
v.reserve(100000);

int num = 0;
int* p = NULL;
for (int i = 0; i < 100000; i++) {
v.push_back(i);
if (p != &v[0]) {
p = &v[0];
num++;
}
}

cout << "num:" << num << endl;
}

int main() {

test01();

system("pause");

return 0;
}

String

String 的本质是一个类

string 与 char * 的区别:

  • char * 是一个指针
  • string 是一个类,类内部封装了 char * ,管理整个字符串,是一个 char * 型的容器

构造函数:

  • string(); //创建一个空的字符串 例如: string str;
    string(const char* s); //使用字符串s初始化
  • string(const string& str); //使用一个string对象初始化另一个string对象
  • string(int n, char c); //使用n个字符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
#include <string>
//string构造
void test01()
{
string s1; //创建空字符串,调用无参构造函数
cout << "str1 = " << s1 << endl;

const char* str = "hello world";
string s2(str); //把c_string转换成了string

cout << "str2 = " << s2 << endl;

string s3(s2); //调用拷贝构造函数
cout << "str3 = " << s3 << endl;

string s4(10, 'a');
cout << "str3 = " << s3 << endl;
}

int main() {

test01();

system("pause");

return 0;
}

string赋值操作

功能描述:

  • 给string字符串进行赋值

赋值的函数原型:

  • string& operator=(const char* s); //char*类型字符串 赋值给当前的字符串
  • string& operator=(const string &s); //把字符串s赋给当前的字符串
  • string& operator=(char c); //字符赋值给当前的字符串
  • string& assign(const char *s); //把字符串s赋给当前的字符串
  • string& assign(const char *s, int n); //把字符串s的前n个字符赋给当前的字符串
  • string& assign(const string &s); //把字符串s赋给当前字符串
  • string& assign(int n, char c); //用n个字符c赋给当前字符串

string字符串拼接

功能描述:

  • 实现在字符串末尾拼接字符串

函数原型:

  • string& operator+=(const char* str); //重载+=操作符
  • string& operator+=(const char c); //重载+=操作符
  • string& operator+=(const string& str); //重载+=操作符
  • string& append(const char *s); //把字符串s连接到当前字符串结尾
  • string& append(const char *s, int n); //把字符串s的前n个字符连接到当前字符串结尾
  • string& append(const string &s); //同operator+=(const string& str)
  • string& append(const string &s, int pos, int n);//字符串s中从pos开始的n个字符连接到字符串结尾

string查找和替换

功能描述:

  • 查找:查找指定字符串是否存在
  • 替换:在指定的位置替换字符串

函数原型:

  • int find(const string& str, int pos = 0) const; //查找str第一次出现位置,从pos开始查找
  • int find(const char* s, int pos = 0) const; //查找s第一次出现位置,从pos开始查找
  • int find(const char* s, int pos, int n) const; //从pos位置查找s的前n个字符第一次位置
  • int find(const char c, int pos = 0) const; //查找字符c第一次出现位置
  • int rfind(const string& str, int pos = npos) const; //查找str最后一次位置,从pos开始查找
  • int rfind(const char* s, int pos = npos) const; //查找s最后一次出现位置,从pos开始查找
  • int rfind(const char* s, int pos, int n) const; //从pos查找s的前n个字符最后一次位置
  • int rfind(const char c, int pos = 0) const; //查找字符c最后一次出现位置
  • string& replace(int pos, int n, const string& str); //替换从pos开始n个字符为字符串str
  • string& replace(int pos, int n,const char* s); //替换从pos开始的n个字符为字符串s

string字符串比较

功能描述:

  • 字符串之间的比较

比较方式:

  • 字符串比较是按字符的ASCII码进行对比

= 返回 0

> 返回 1

< 返回 -1

函数原型:

  • int compare(const string &s) const; //与字符串s比较
  • int compare(const char *s) const; //与字符串s比较

string字符存取

string中单个字符存取方式有两种

  • char& operator[](int n); //通过[]方式取字符
  • char& at(int n); //通过at方法获取字符

string插入和删除

功能描述:

  • 对string字符串进行插入和删除字符操作

函数原型:

  • string& insert(int pos, const char* s); //插入字符串
  • string& insert(int pos, const string& str); //插入字符串
  • string& insert(int pos, int n, char c); //在指定位置插入n个字符c
  • string& erase(int pos, int n = npos); //删除从Pos开始的n个字符

string子串

功能描述:

  • 从字符串中获取想要的子串

函数原型:

  • string substr(int pos = 0, int n = npos) const; //返回由pos开始的n个字符组成的字符串

Deque容器

功能:双端数组,可以对头端进行插入删除操作

deque 与 vector 的区别:

  • vector 对于头部的插入删除效率低,数据量越大,效率越低
  • 对于头部的插入删除速度会比 vector 快
  • vector 访问元素时的速度会比 deque 快

Screen Shot 2021-03-11 at 20.47.44

Screen Shot 2021-03-11 at 20.48.04

deque 内部有个中控器,维护每段缓冲区中的内容,缓冲区中存放真是数据,中控器维护的是每个缓冲区的地址,使得使用 deque 时像一片连续的内存空间

deque 构造函数

函数原型:

  • deque<T> deqT; //默认构造形式
  • deque(beg, end); //构造函数将[beg, end)区间中的元素拷贝给本身。
  • deque(n, elem); //构造函数将n个elem拷贝给本身。
  • deque(const deque &deq); //拷贝构造函数
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
#include <deque>

void printDeque(const deque<int>& d)
{
for (deque<int>::const_iterator it = d.begin(); it != d.end(); it++) {
cout << *it << " ";

}
cout << endl;
}
//deque构造
void test01() {

deque<int> d1; //无参构造函数
for (int i = 0; i < 10; i++)
{
d1.push_back(i);
}
printDeque(d1);
deque<int> d2(d1.begin(),d1.end());
printDeque(d2);

deque<int>d3(10,100);
printDeque(d3);

deque<int>d4 = d3;
printDeque(d4);
}

int main() {

test01();

system("pause");

return 0;
}

deque 大小操作

deque 无容量限制,不像 vector 有限制,所以没有 .capacity 接口

函数原型:

  • deque.empty(); //判断容器是否为空

  • deque.size(); //返回容器中元素的个数

  • deque.resize(num); //重新指定容器的长度为num,若容器变长,则以默认值填充新位置。

    ​ //如果容器变短,则末尾超出容器长度的元素被删除。

  • deque.resize(num, elem); //重新指定容器的长度为num,若容器变长,则以elem值填充新位置。

    ​ //如果容器变短,则末尾超出容器长度的元素被删除。

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
#include <deque>

void printDeque(const deque<int>& d)
{
for (deque<int>::const_iterator it = d.begin(); it != d.end(); it++) {
cout << *it << " ";
}
cout << endl;
}

//大小操作
void test01()
{
deque<int> d1;
for (int i = 0; i < 10; i++)
{
d1.push_back(i);
}
printDeque(d1);

//判断容器是否为空
if (d1.empty()) {
cout << "d1为空!" << endl;
}
else {
cout << "d1不为空!" << endl;
//统计大小
cout << "d1的大小为:" << d1.size() << endl;
}

//重新指定大小
d1.resize(15, 1);
printDeque(d1);

d1.resize(5);
printDeque(d1);
}

int main() {

test01();

system("pause");

return 0;
}

deque 插入和删除

函数原型:

两端插入操作:

  • push_back(elem); //在容器尾部添加一个数据
  • push_front(elem); //在容器头部插入一个数据
  • pop_back(); //删除容器最后一个数据
  • pop_front(); //删除容器第一个数据

指定位置操作:

  • insert(pos,elem); //在pos位置插入一个elem元素的拷贝,返回新数据的位置。

  • insert(pos,n,elem); //在pos位置插入n个elem数据,无返回值。

  • insert(pos,beg,end); //在pos位置插入[beg,end)区间的数据,无返回值。

  • clear(); //清空容器的所有数据

  • erase(beg,end); //删除[beg,end)区间的数据,返回下一个数据的位置。

  • erase(pos); //删除pos位置的数据,返回下一个数据的位置。

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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#include <deque>

void printDeque(const deque<int>& d)
{
for (deque<int>::const_iterator it = d.begin(); it != d.end(); it++) {
cout << *it << " ";

}
cout << endl;
}
//两端操作
void test01()
{
deque<int> d;
//尾插
d.push_back(10);
d.push_back(20);
//头插
d.push_front(100);
d.push_front(200);

printDeque(d);

//尾删
d.pop_back();
//头删
d.pop_front();
printDeque(d);
}

//插入
void test02()
{
deque<int> d;
d.push_back(10);
d.push_back(20);
d.push_front(100);
d.push_front(200);
printDeque(d);

d.insert(d.begin(), 1000);
printDeque(d);

d.insert(d.begin(), 2,10000);
printDeque(d);

deque<int>d2;
d2.push_back(1);
d2.push_back(2);
d2.push_back(3);

d.insert(d.begin(), d2.begin(), d2.end());
printDeque(d);

}

//删除
void test03()
{
deque<int> d;
d.push_back(10);
d.push_back(20);
d.push_front(100);
d.push_front(200);
printDeque(d);

d.erase(d.begin());
printDeque(d);

d.erase(d.begin(), d.end());
d.clear();
printDeque(d);
}

int main() {

//test01();

//test02();

test03();

system("pause");

return 0;
}

总结:

  • 尾插 push_back( )
  • 尾删 pop_back( )
  • 头插 push_front( )
  • 头删 pop_front( )

deque 容器数据存取

函数原型:

  • at(int idx); //返回索引idx所指的数据
  • operator[]; //返回索引idx所指的数据
  • front(); //返回容器中第一个数据元素
  • back(); //返回容器中最后一个数据元素
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
#include <deque>

void printDeque(const deque<int>& d)
{
for (deque<int>::const_iterator it = d.begin(); it != d.end(); it++) {
cout << *it << " ";

}
cout << endl;
}

//数据存取
void test01()
{

deque<int> d;
d.push_back(10);
d.push_back(20);
d.push_front(100);
d.push_front(200);

for (int i = 0; i < d.size(); i++) {
cout << d[i] << " ";
}
cout << endl;


for (int i = 0; i < d.size(); i++) {
cout << d.at(i) << " ";
}
cout << endl;

cout << "front:" << d.front() << endl;

cout << "back:" << d.back() << endl;

}

int main() {

test01();

system("pause");

return 0;
}

deque 排序

算法

利用 sort 算法进行对 deque 的排序

sort 算法也可以对 vector 等进行排序

  • sort(iterator begin, iterator end)
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 <iostream>
#include <deque>
#include <algorithm>
using namespace std;

void printDeque(const deque<int> &d){
for(deque<int>::const_iterator it = d.begin(); it != d.end(); it++ ){
cout<< *it <<" ";
}
cout <<endl;
}

void test01(){
deque<int> d;
d.push_back(30);
d.push_back(40);
d.push_back(100);
d.push_front(200);
d.push_front(300);
printDeque(d);

sort(d.begin(), d.end());
cout<<"after sort"<<endl;
printDeque(d);

}

int main(){

test01();

return 0;
}

rand 随机数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>
#include <ctime>
using namespace std;

int main(){
//srand( (unsigned int)time(NULL)); // 用以代替下面三行
unsigned int seed;
seed = time(0);
srand(seed);

int first = rand() % 41 + 10;
cout << first <<endl;
int second = rand() % 41 + 10;
cout << second <<endl;

return 0;
}

stack

构造函数:

  • stack<T> stk; //stack采用模板类实现, stack对象的默认构造形式
  • stack(const stack &stk); //拷贝构造函数

赋值操作:

  • stack& operator=(const stack &stk); //重载等号操作符

数据存取:

  • push(elem); //向栈顶添加元素
  • pop(); //从栈顶移除第一个元素
  • top(); //返回栈顶元素

大小操作:

  • empty(); //判断堆栈是否为空
  • size(); //返回栈的大小

queue

构造函数:

  • queue<T> que; //queue采用模板类实现,queue对象的默认构造形式
  • queue(const queue &que); //拷贝构造函数

赋值操作:

  • queue& operator=(const queue &que); //重载等号操作符

数据存取:

  • push(elem); //往队尾添加元素
  • pop(); //从队头移除第一个元素
  • back(); //返回最后一个元素
  • front(); //返回第一个元素

大小操作:

  • empty(); //判断堆栈是否为空
  • size(); //返回栈的大小

list

功能描述:

  • 创建list容器

函数原型:

  • list<T> lst; //list采用采用模板类实现,对象的默认构造形式:
  • list(beg,end); //构造函数将[beg, end)区间中的元素拷贝给本身。
  • list(n,elem); //构造函数将n个elem拷贝给本身。
  • list(const list &lst); //拷贝构造函数。
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
#include <list>

void printList(const list<int>& L) {

for (list<int>::const_iterator it = L.begin(); it != L.end(); it++) {
cout << *it << " ";
}
cout << endl;
}

void test01()
{
list<int>L1;
L1.push_back(10);
L1.push_back(20);
L1.push_back(30);
L1.push_back(40);

printList(L1);

list<int>L2(L1.begin(),L1.end());
printList(L2);

list<int>L3(L2);
printList(L3);

list<int>L4(10, 1000);
printList(L4);
}

int main() {

test01();

system("pause");

return 0;
}

list 成员函数

insert() 它将新元素插入到迭代器指向的位置之前。
push_back() 它在容器的末尾添加了一个新元素。
push_front() 它在前面增加了一个新元素。
pop_back() 删除最后一个元素。
pop_front() 删除第一个元素。
empty() 它检查列表是否为空。
size() 它查找列表中存在的元素数。
max_size() 它找到列表的最大大小。
front() 它返回列表的第一个元素。
back() 它返回列表的最后一个元素。
swap() 当两个列表的类型相同时,它将交换两个列表。
reverse() 它反转了列表的元素。
sort() 它以递增的顺序对列表中的元素进行排序。
merge() 它合并两个排序的列表。
splice() 它将新列表插入到调用列表中。
unique() 它从列表中删除所有重复的元素。
resize() 它更改列表容器的大小。
assign() 它将一个新元素分配给列表容器。
emplace() 它将在指定位置插入一个新元素。
emplace_back() 它将在容器的末尾插入一个新元素。
emplace_front() 它将在列表的开头插入一个新元素。
remove(element) 删除容器中所有与 elem 值匹配的元素

list 数据存取

功能描述:

  • 对list容器中数据进行存取

函数原型:

  • front(); //返回第一个元素。
  • back(); //返回最后一个元素。

list 反转和排序

功能描述:

  • 将容器中的元素反转,以及将容器中的数据进行排序

函数原型:

  • reverse(); //反转链表
  • sort(); //链表排序
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
void printList(const list<int>& L) {

for (list<int>::const_iterator it = L.begin(); it != L.end(); it++) {
cout << *it << " ";
}
cout << endl;
}

bool myCompare(int val1 , int val2)
{
return val1 > val2;
}

//反转和排序
void test01()
{
list<int> L;
L.push_back(90);
L.push_back(30);
L.push_back(20);
L.push_back(70);
printList(L);

//反转容器的元素
L.reverse();
printList(L);

//排序
L.sort(); //默认的排序规则 从小到大
printList(L);

L.sort(myCompare); //指定规则,从大到小
printList(L);
}

int main() {

test01();

system("pause");

return 0;
}

list 排序案例

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
#include <list>
#include <string>
class Person {
public:
Person(string name, int age , int height) {
m_Name = name;
m_Age = age;
m_Height = height;
}

public:
string m_Name; //姓名
int m_Age; //年龄
int m_Height; //身高
};


bool ComparePerson(Person& p1, Person& p2) {

if (p1.m_Age == p2.m_Age) {
return p1.m_Height > p2.m_Height;
}
else
{
return p1.m_Age < p2.m_Age;
}

}

void test01() {

list<Person> L;

Person p1("刘备", 35 , 175);
Person p2("曹操", 45 , 180);
Person p3("孙权", 40 , 170);
Person p4("赵云", 25 , 190);
Person p5("张飞", 35 , 160);
Person p6("关羽", 35 , 200);

L.push_back(p1);
L.push_back(p2);
L.push_back(p3);
L.push_back(p4);
L.push_back(p5);
L.push_back(p6);

for (list<Person>::iterator it = L.begin(); it != L.end(); it++) {
cout << "姓名: " << it->m_Name << " 年龄: " << it->m_Age
<< " 身高: " << it->m_Height << endl;
}

cout << "---------------------------------" << endl;
L.sort(ComparePerson); //排序

for (list<Person>::iterator it = L.begin(); it != L.end(); it++) {
cout << "姓名: " << it->m_Name << " 年龄: " << it->m_Age
<< " 身高: " << it->m_Height << endl;
}
}

int main() {

test01();

system("pause");

return 0;
}

关联式容器

使用关联式容器存储的元素,都是一个个的“键值对”( ) 。

C++ STL提供了4种关联式容器,map, set, multimap, multiset

关联式容器名称 特点
map 定义在 \ 头文件中,使用该容器存储的数据,其各个元素的键值必须是唯一的(不能重复),该容器会根据键值的大小进行自动升序排序
set 定义在 \ 头文件中,使用该容器存储的数据,各个元素键值完全相同,且各个元素的值不能重复(保证各元素键的唯一性),该容器会根据键值的大小进行自动升序排序。
multimap 与 map 区别在于,键值可以重复
multiset 存储的元素的值可以重复(一旦重复,意味着键也是重复的)

pair 类模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
#include <utility> // pair
#include <string> // string
using namespace std;
int main() {
// 调用构造函数 1,也就是默认构造函数
pair <string, double> pair1;
// 调用第 2 种构造函数
pair <string, string> pair2("STL教程","http://stl/");
// 调用拷贝构造函数
pair <string, string> pair3(pair2);
//调用移动构造函数
pair <string, string> pair4(make_pair("C++教程", "http://cplus/"));
// 调用第 5 种构造函数
pair <string, string> pair5(string("Python教程"), string("http://python/"));

cout << "pair1: " << pair1.first << " " << pair1.second << endl;
cout << "pair2: "<< pair2.first << " " << pair2.second << endl;
cout << "pair3: " << pair3.first << " " << pair3.second << endl;
cout << "pair4: " << pair4.first << " " << pair4.second << endl;
cout << "pair5: " << pair5.first << " " << pair5.second << endl;
return 0;
}

1
2
3
4
5
pair1:  0
pair2: STL教程 http://stl/
pair3: STL教程 http://stl/
pair4: C++教程 http://cplus/
pair5: Python教程 http://python/

在 pair4 中,make_pair() 是 \ 头文件中提供的,主要功能是生成一个 pair 对象,使得 make_pair 函数所创造的返回值作为一个临时对象传递给 pair() 的第4种构造函数—> 移动构造函数(不是拷贝构造函数)。

pair 的赋值操作

1
2
pair1.first = "C++";
pair1.second = "https://cplus.com";
1
pair<string, string> pair = make_pair("c++","https://cplus.com");

\ 还提供了对于 pair 对象重载了 <, <=, >, >==, ==, != 运算符,可以进行比较大小之类的操作。

1
2
3
4
5
6
7
pair<string, int> pair1("C++", 100);
pair<string, int> pair2("C++", 100);
if(pair1 == pair2){
std::cout<< pair1 == pair2 <<std::endl;
}else{
std::cout<< pair1 != pair2 <<std::endl;
}

无序关联式容器 unordered_map

无序关联式容器与关联式容器最主要的区别在于,无序关联式容器不会对存储的元素做默认的升序排序操作。

无序关联式容器擅长通过指定键来查找对应的值,而遍历容器中存储元素的效率不如关联式容器。

关联式容器的底层实现采用的树存储结构——红黑树结构。

无序容器的底层采用的是哈希表的存储结构。

无序容器 功能
unordered_map 存储键值对 类型的元素,其中各个键值对键的值不允许重复,且该容器中存储的键值对是无序的。
unordered_multimap 和 unordered_map 唯一的区别在于,该容器允许存储多个键相同的键值对。
unordered_set 不再以键值对的形式存储数据,而是直接存储数据元素本身(当然也可以理解为,该容器存储的全部都是键 key 和值 value 相等的键值对,正因为它们相等,因此只存储 value 即可)。另外,该容器存储的元素不能重复,且容器内部存储的元素也是无序的。
unordered_multiset 和 unordered_set 唯一的区别在于,该容器允许存储值相同的元素。
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
#include <iostream>
#include <string>
#include <unordered_map>
using namespace std;

int main()
{
//创建并初始化一个 unordered_map 容器,其存储的 <string,string> 类型的键值对
std::unordered_map<std::string, std::string> myMap;
myMap["C++"] = "cpp";
myMap["Py"] = "python";
myMap["Java"] = "java";

//查找指定键对应的值,效率比关联式容器高
string str = myMap.at("Java");
cout << "str = " << str << endl;

//使用迭代器遍历哈希容器,效率不如关联式容器
for (auto iter = myMap.begin(); iter != myMap.end(); ++iter)
{
//pair 类型键值对分为 2 部分
cout << iter->first << " " << iter->second << endl;
}
return 0;
}

1
2
3
4
5
6
7
8
unorderedMaptest.cpp:19:10: warning: 'auto' type specifier is a C++11 extension [-Wc++11-extensions]
for (auto iter = myMap.begin(); iter != myMap.end(); ++iter)
^
1 warning generated.
str = java
Java java
Py python
C++ cpp

向 unordered_map 中插入元素的方法

  • insert
  • emplace

emplace 方法效率比 insert 高

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>
#include <string>
#include <unordered_map>
using namespace std;
int main()
{
//创建 umap 容器
unordered_map<string, string> umap;
//定义一个接受 emplace() 方法的 pair 类型变量
pair<unordered_map<string, string>::iterator, bool> ret;
//调用 emplace() 方法
ret = umap.emplace("STL教程", "http://c.biancheng.net/stl/");
//输出 ret 中包含的 2 个元素的值
cout << "bool =" << ret.second << endl;
cout << "iter ->" << ret.first->first << " " << ret.first->second << endl;
return 0;
}

tuple

tuple<>为pair模板的泛化。

Morden 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
//EX
//Before
return vector<int>(begin(ans),end(ans));
//After
return {begin(ans), end(ans)};

//EX
std::unique_ptr<Foo> p = std::make_unique<Foo>(...);
auto p = std::make_unique<Foo>(...);

//EX
for(auto it = s.begin(); it != s.end(); ++it) ...
auto it = s.find(x);

//EX
map<string, int> m;
for(auto&& kv : m){
cout<< kv.first<<" " << kv.secnod << endl;
}
for(auto&&[key, val] : m){
cout<<key<<" " << val <<endl;
}


//EX Since C++17
int a = 3, b =4;
auto[x, y] = std::tie(a, b); //# python: x, y = a, b
//==
int &x = a;
int &y = b;

lambda

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
bool comp(const pair<int, int>&a, const pair<int,int>&b){
return a.second < b.second;
}
int main(){
vector<pair<int,int>> v = ...;
std::sort(begin(v), end(v), comp);
}
//After:
std::sort(begin(v), end(v), [](const auto& a, const auto& b){
return a.second < b.second;
});


//EX
int a = 1;
auto foo = [a]( a += 1);
foo();
ASSERT(a==1);

//EX
int a = 1;
auto foo = [&a]( a += 1);
foo();
ASSERT(a==2);

//EX
int a, b, c;
auto foo = [=]{return a + b + c; }

auto foo = [=, &a]{ a += b + c; }

new 内存分配

1
2
3
4
5
6
7
8
9
10
11
void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{ // try to allocate size bytes
void *p;
while ((p = malloc(size)) == 0)
if (_callnewh(size) == 0)
{ // report no memory
_THROW_NCEE(_XSTD bad_alloc, );
}

return (p);
}

new 调用C语言标准库的 malloc 进行内存分配,并且在内存分配失败的时候会调用 _callnewh 来调用我们自定义的 new-handler,如果 new-handler 返回非零,new 运算符就会再次尝试内存分配,直到分配内存或 new-handler 返回了零,那么 new 此时将会以抛异常的形式来表示内存分配失败。

0拷贝

概念:一种避免CPU将数据从一块存储拷贝到另一块存储的技术,是指将数据直接从磁盘文件复制到网卡设备中,不需要经由程序操作。

好处:避免操作系统内核缓冲区之间进行数据拷贝操作;避免操作系统内核和用户应用程序地址空间两者之间进行数据拷贝操作。

实现及原理:

  1. mmap() + write().

系统调用函数把内核缓冲区数据映射到用户空间,DMA将磁盘数据拷贝到内核缓冲区,应用进程跟随操作系统内核共享这个缓冲区。应用进程再次调用 write() ,操作系统直接将内核缓冲区的数据拷贝到 socket 缓冲区中,一切过程都发生在内核态。

  1. sendfile()

Linux 内核2.1中,提供专门发送文件的系统调用 sendfile() 。

  • 由sendfile() 发起系统调用,操作系统由用户态空间切换到内核态空间
  • 通过DMA引擎将数据从磁盘拷贝到内核态空间的输入socket缓冲区
  • 将数据从内核空间拷贝到与之关联的socket缓冲区
  • 将socket缓冲区的数据拷贝到协议引擎中
  • sendfile()系统调用结束,操作系统由用户态切换到内核态空间。

inline define 直接调用的区别

  • define 宏定义是单纯的字符替换,inline为代码嵌入(无函数调用跳转,无栈帧小号)
  • 处理阶段,define在预处理阶完成字符替换,inline 在编译阶段完成
  • 类型安全检查,define 无安全类型检查,inline 是一个函数,会进行类型安全检查

进程 线程 协程

干货 | 进程、线程、协程 10 张图讲明白了! - 知乎 (zhihu.com)

img

地址空间分布

操作系统采用虚拟内存技术,把进程虚拟地址空间划分为用户空间和内核空间。

img

用户空间

img

Linux 可用 size 命令查看编译后程序的各个内存区域大小

1
2
3
(base) root@cy:~/rust_Pro# size hello_world/target/release/hello_world
text data bss dec hex filename
341457 13448 536 355441 56c71 hello_world/target/release/hello_world

内核空间

x86 32位系统,Linux 内核地址空间指虚拟地址从 0xC0000000开始到 0xFFFFFFFF位置的高端内存地址空间,总容量1G的容量。包含,内核镜像、物理页面表、驱动程序等运行在内核空间。

进程

进程是程序执行的过程,包括了动态创建、调度和消亡的整个过程,进程是程序资源管理的最小单位

线程

线程是操作系统能够进行运算调度的最小单位。线程被包含在进程之中,是进程中实际运作单位,一个进程可以包含多个线程,线程是资源调度的最小单位

线程资源和开销

同一进程中多条线程共享全部该进程中的系统资源,如虚拟地址空间,文件描述符文件描述符和信号处理等。但同一进程中多个线程有各自的调用栈、寄存器环境、线程本地存储等。

线程创建的开销主要是线程堆栈的建立,分配内存的开销,开销不大,其中最大的开销发生在线程上下文切换的时候。

img

协程:轻量化的微线程。

协程的调度完全由用户控制,协程拥有自己的寄存器上下文和栈,协程调度切换时,将寄存器上下文和栈保存到其他地方,再切回来时,回复先前保存的寄存器上下文和栈,直接将操作用户空间栈,完全没有内核切换的开销。

动态协程:协程的栈大小2KB,最大到1GB,可动态伸缩。而线程存储上下文寄存器和栈固定为2MB,不灵活且相对较大。

CATALOG
  1. 1. CPP refresh
    1. 1.1. 内存模型
      1. 1.1.1. 代码区
      2. 1.1.2. 全局区
      3. 1.1.3. 栈区
      4. 1.1.4. 堆区
      5. 1.1.5. 程序运行前
      6. 1.1.6. 程序运行后
      7. 1.1.7. new 操作符
    2. 1.2. 引用
      1. 1.2.1. 引用可以作为函数的返回值存在
      2. 1.2.2. 引用的本质
      3. 1.2.3. 常量引用
    3. 1.3. 函数提高
      1. 1.3.1. 函数占位参数示例
      2. 1.3.2. 函数重载
      3. 1.3.3. 函数重载注意事项
    4. 1.4. 类和对象
      1. 1.4.1. 封装权限:
      2. 1.4.2. struct 与 class 区别
      3. 1.4.3. 对象的初始化和清理
      4. 1.4.4. 构造函数中的拷贝构造函数
      5. 1.4.5. 拷贝构造函数调用时机
      6. 1.4.6. 构造函数调用规则
      7. 1.4.7. 深拷贝与浅拷贝
      8. 1.4.8. 初始化列表
      9. 1.4.9. 类对象作为类成员
      10. 1.4.10. 静态成员
      11. 1.4.11. this 指针指向被调用的成员函数所属的对象
      12. 1.4.12. 空指针访问成员函数
      13. 1.4.13. const 修饰成员函数
    5. 1.5. 友元
    6. 1.6. 操作符重载
      1. 1.6.1. 加号运算符重载
      2. 1.6.2. 左移运算符 << 重载
      3. 1.6.3. 递增运算符重载
      4. 1.6.4. 赋值运算符(=)的重载
      5. 1.6.5. 关系运算符(==)重载
      6. 1.6.6. 函数调用运算符 () 重载
    7. 1.7. 继承
      1. 1.7.1. 继承中的对象模型
      2. 1.7.2. 继承中构造和析构顺序
      3. 1.7.3. 继承中同名成员的处理方式
      4. 1.7.4. 继承同名静态成员处理方式
    8. 1.8. 多继承语法
      1. 1.8.1. 菱形继承问题的解决方式
    9. 1.9. 多态
      1. 1.9.1. 纯虚函数和抽象类
      2. 1.9.2. 多态案例,制作饮品
      3. 1.9.3. 虚析构和纯虚析构
      4. 1.9.4. 多态案例:电脑组装
    10. 1.10. 文件操作
      1. 1.10.1. 写文件
      2. 1.10.2. 读文件
      3. 1.10.3. 二进制文件
    11. 1.11. C++ 泛型概念(模板)
      1. 1.11.1. 函数模板语法
        1. 1.11.1.1. 函数模板注意事项
        2. 1.11.1.2. 函数模板案例:选择排序
        3. 1.11.1.3. 普通函数与函数模板的区别
        4. 1.11.1.4. 模板的局限性
      2. 1.11.2. 类模板
      3. 1.11.3. 类模板与函数模板区别
      4. 1.11.4. 类模板中成员函数创建时机
      5. 1.11.5. 类模板对象作函数参数
      6. 1.11.6. 类模板与继承
      7. 1.11.7. 类模板成员函数与类外实现
      8. 1.11.8. 类模板分文件编写
      9. 1.11.9. 类模板案例
    12. 1.12. STL
    13. 1.13. vector
      1. 1.13.1. vector容器中存放自定义数据类型
      2. 1.13.2. vector 容器嵌套容器
      3. 1.13.3. 插入和删除
      4. 1.13.4. swap
      5. 1.13.5. 利用 reserve 预留空间
    14. 1.14. String
      1. 1.14.1. string赋值操作
      2. 1.14.2. string字符串拼接
      3. 1.14.3. string查找和替换
      4. 1.14.4. string字符串比较
      5. 1.14.5. string字符存取
      6. 1.14.6. string插入和删除
      7. 1.14.7. string子串
    15. 1.15. Deque容器
      1. 1.15.1. deque 构造函数
      2. 1.15.2. deque 大小操作
      3. 1.15.3. deque 插入和删除
      4. 1.15.4. deque 容器数据存取
      5. 1.15.5. deque 排序
      6. 1.15.6. stack
      7. 1.15.7. queue
    16. 1.16. list
      1. 1.16.1. list 数据存取
      2. 1.16.2. list 反转和排序
      3. 1.16.3. list 排序案例
    17. 1.17. 关联式容器
      1. 1.17.1. pair 类模板
    18. 1.18. 无序关联式容器 unordered_map
      1. 1.18.1. tuple
    19. 1.19. Morden C++
      1. 1.19.1. lambda
    20. 1.20. new 内存分配
    21. 1.21. 0拷贝
    22. 1.22. inline define 直接调用的区别
    23. 1.23. 进程 线程 协程