重学C++

基础语法

数据类型

image-20220824153813162

编译过程

编译型语言一般需要经历编译和链接的过程,才能变成真正可执行的程序

image-20220824122009971

标识符

image-20220824161837695

关键字

image-20220824162003345

常量

image-20220824162858481 image-20220824163545302 image-20220824163806810

杂项运算符

image-20220825093620043

字节序

一般的家用电脑是小端法

image-20220825103115632

补码

image-20220826000930719 image-20220825104706156 image-20220825105134310

右移运算

image-20220825105607497

数组

image-20220825110308159

字符串

image-20220825114332076 image-20220825115134169 image-20220825121529205

字符串的指针表示方法

image-20220825154539870

字符串常见操作

  • strlen()求字符串有效长度,不包括’\0’

    1
    2
    char a[] = "123";
    cout << strlen(a) << endl; // 3
  • sizeof()求字符串占用空间,包括’\0’

    1
    2
    char a[] = "123";
    cout << sizeof(a) << endl; // 4
  • strcmp(s1, s2)比较s1和s2的大小,逐个字符按照ASCII码比较

    1
    2
    3
    4
    5
    6
    char a[] = "A";
    char b[] = "B";
    char c[] = "B";
    cout << strcmp(a, b) << endl; // -1
    cout << strcmp(b, a) << endl; // 1
    cout << strcmp(b, c) << endl; // 0
  • strcpy(s1, s2)复制字符串s2到s1

  • strncpy(s1, s2, n)指定字符串长度复制

    1
    2
    3
    4
    5
    6
    7
    8
    9
    char a[100] = { 0 };
    char b[] = "hello";
    char c[] = "world";
    strcpy(a, b);
    cout << a << endl; // hello
    strcpy(a, b);
    cout << a << endl; // hello
    strncpy(a, c, 1);
    cout << a << endl; // wello
  • strcat(s1, s2)字符串拼接,将s2接到s1后面

  • strchr(s1, ch)指向字符串s1中ch第一次出现的位置

    1
    2
    3
    char a[] = "A";
    char b[] = "ABC";
    cout << strchr(b, 'B') << endl; // BC
  • strstr(s1, s2)指向字符串s1中字符串s2第一次出现的位置

    1
    2
    3
    char a[] = "A";
    char b[] = "ABC";
    cout << strstr(b, a) << endl; // ABC

安全版避免缓冲区溢出

  • strcpy_s()是安全版strcpy()
  • strcat_s()是安全版strcat
  • strnlen_s()
  • strncpy_s()

常见bug

image-20220825212728685

新型字符串string

image-20220825213209639

相关函数

获取字符串长度:

  • s.lenth()
  • s.size()
  • s.capacity()默认容量是15

string转换为字符数组

  • s.c_str()

    1
    2
    3
    4
    string s = "123";
    string s1 = "456";
    const char *c = s.c_str();
    cout << c << endl;

指针/引用

指针就使用->,对象就使用.运算符

image-20220825215432922 image-20220825215650435 image-20220825220002315

指针的定义和间接访问

image-20220825220142143

数组与指针

image-20220825231011088

左值与右值

image-20220825231310410

几种原始指针

数组指针和指针数组

image-20220825231749493

image-20220825232909168

const与指针

image-20220825233558775

image-20220825233715154

指向指针的指针

image-20220825221450933

未初始化和非法指针

image-20220825235546562

NULL指针

image-20220826000040121

image-20220826000413458

野指针

image-20220826001609481

原始指针的基本运算

指针默认是一个16字节的整型数,例如 char* 就是一个16字节的整型数,int* 也是

image-20220825224612569

image-20220825225538394

1
2
3
4
char b = '1';
char* c = &b;
*c = 'b';
cout << *c << endl; // b
image-20220825230133351

指针++,–的汇编

eax和ecx是寄存器

以char* cp2 = ++cp; 为例子

汇编的意思是:

将cp指针的值,放到eax寄存器里面

将eax寄存器里面的值+1

将eax的值存回cp

将cp的值存到ecx寄存器

将ecx寄存器的值存到cp2中

image-20220826094806925

image-20220826100023755

image-20220826104227139

image-20220826105243146

反汇编

打断点,f5运行的时候,右击鼠标,可以转到反汇编

image-20220826105605437 image-20220826105731965

c++存储区域划分

每行代码在内存环境中的具体位置

image-20220826120701422
  • 栈中的空间是从大地址往小地址分配的,先定义的变量,地址越大

image-20220826121441014

  • 堆中空间是从小往大分配的
  • 字符串在常量区,变量在栈区,常量区的信息是不能改变的

image-20220826141531839

image-20220826142121286

RAII

image-20220826142943441

堆对比栈

image-20220826143256868

静态存储区对比常量存储区

image-20220826144333457

内存泄漏

image-20220826144708447

比指针更安全的解决方案

智能指针

image-20220826150050781

auto_ptr

1
2
auto_ptr<int> pl(new int(10));
cout << *pl << endl; // 10
image-20220826150344061

unique_ptr

image-20220826154933195

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

int main()
{
// unique_ptr,make_unique会返回unique_ptr的指针
auto w = std::make_unique<int>(10);

// w.get()返回的是w这个智能指针中对应的指向堆中开辟了10这个内存块的指针
cout << *(w.get()) << endl; // 10

// 编译错误如果想要把w复制给w2,是不可以的
// auto w2 = w;

// unique_ptr只支持移动语义,w2获得内存所有权,w此时等于nullptr
auto w2 = std::move(w);

cout << (w.get() != nullptr ? *(w.get()) : -1) << endl; // -1
cout << (w2.get() != nullptr ? *(w2.get()) : -1) << endl; // 10
return 0;
}
1
2
3
4
5
// i 变量在栈空间中,出了这个局部{ },i指针就失效了
// 那么new出来的对象,在堆当中,会不会失效呢?也会
{
auto i = unique_ptr<int>(new int(10));
}

shared_ptr

image-20220826164654895

引用计数可能的bug:循环引用会导致堆里的内存无法正常回收,造成内存泄漏

image-20220826164830679 image-20220826214031279 image-20220826214331987

weak_ptr

weak_ptr被设计为与shared_ptr共同工作,用一种观察者模式工作

image-20220826165041268

引用

image-20220826215620582 image-20220826220041563

断言assert

1
2
头文件
#include <assert.h>
1
assert() // 如果程序运行到assert的时候,括号中的条件不为真,程序就会出错

有了指针为什么还需要引用

Bjarne Stroustrup的解释:为了支持函数运算符重载

有了引用为什么还需要指针

Bjarne Stroustrup的解释:为了兼容C语言

image-20220826222100954

基础句法

switch与if

从汇编代码来看,多分支的switch的效率会比if更高一些

image-20220828102525742

枚举

声明的时候,没有实际的空间存储它

但是当定义wT weekday;的时候,就会分配实际的空间存储它了

image-20220828104451763 image-20220828105531532
1
2
3
4
5
6
7
8
9
10
11
12
13
int main()
{
enum wT { Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday }; // 声明wT类型
wT weekday;
weekday = Monday;
weekday = Tuesday;
// weekday = 0; 不能直接给int值,只能赋值成wT定义好的类型值
cout << weekday << endl;
// Monday = 0; // 类型值不能做左值
int a = Wednesday;
cout << a << endl;
return 0;
}

结构体与联合体

结构体的数据对齐问题

image-20220828110657649

结构体的内存布局

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
int main()
{
// 联合体,共同取同一个空间,也就是double的空间8
union Score
{
double ds;
char level;
};
// 结构体
struct Student
{
char name[6];
int age;
Score s;
};

cout << sizeof(Score) << endl; // 8

Student s1;
strcpy_s(s1.name, "lili");
s1.age = 16;
s1.s.ds = 95.5;
s1.s.level = 'A';

cout << sizeof(Student) << endl; // 24

return 0;
}

结构体的内存一定是其中最大的元素的整数倍

内存布局不是紧密的

image-20220828111303214
  • 头文件上加#pragma pack(1)可以让内存分配紧密相连
image-20220828112422468

函数

函数名和参数列表一起构成了函数签名

image-20220828154128651

例如:int(*p)(int, int);,这行代码定义了一个可以指向返回值为整型且有两个整型形参函数的指针变量p,符合返回值为整型且有两个整型形参的函数都可以将其地址(即其函数名)赋给p。

在使用指向函数的指针变量时,只需要将函数名赋给指向函数的指针变量即可,因为函数名就是该函数的入口地址

由于指向函数的指针变量保存了函数的地址,则该指针变量就指向了对应的函数。例如,求最大值的函数命名为max,如果将其函数名赋给指向函数的指针变量p(即p = max)后,则p就指向了max函数,并且可以通过(*p)(a, b);的方式来调用max函数,因为指针变量p保存了max函数的地址,那么*p就是max。需要注意的是,其中*p前的*可以省略,故也可以写成p(a, b);

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

using namespace std;

int MaxValue(int x, int y)
{
return (x > y) ? x : y;
}

int MinValue(int x, int y)
{
return (x < y) ? x : y;
}

int Add(int x, int y)
{
return x + y;
}

bool ProcessNum(int x, int y, int(*p)(int a, int b))
{
cout << p(x, y) << endl;
return true;
}

int main()
{
int x = 10, y = 20;
cout << ProcessNum(x, y, MaxValue) << endl;
cout << ProcessNum(x, y, MinValue) << endl;
cout << ProcessNum(x, y, Add) << endl;

return 0;
}

命名空间

image-20220828154453079

声明函数:在stdafx.h中

image-20220828154819249

定义函数:在stdafx.cpp中

image-20220828154846618
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>

using namespace std;

int main()
{
int(*p)(int);
p = test; // 也可以定义的时候直接 int(*p)(int) = test;
int result = (*p)(1); // 这里*可以省略 p(1)
cout << result << endl; // 1

result = quickzhao::test(1);
cout << result << endl; // 2
return 0;
}

内联函数

image-20220828162853232

先用vs设置一下:

image-20220828163152588

但是编译器有可能不会用内联

递归

image-20220828165152140

面向对象(实现Complex)

image-20220828173107169 image-20220905144429268

新建一个类:

image-20220828181941426

构造函数与析构函数

ConsoleApplication1项目为完整代码

下面是以Complex复数为例,实现运算操作:

函数的声明在Complex.h文件中,实现在Complex.cpp文件中

默认构造函数:如果没有自己指定有参构造,系统则会自动创建默认构造函数

有参构造函数:如果自定义了有参构造,那么就需要显示的写出默认构造函数

声明:

1
2
3
4
5
6
7
class Complex
{
public:
Complex(); // 默认构造函数
Complex(double r, double i); // 构造函数
virtual ~Complex(); // 析构函数
}

实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Complex::Complex()
{
_real = 0.0;
_image = 0.0;
cout << "Complex::Complex()" << endl;
}

Complex::Complex(double r, double i)
{
_real = r;
_image = i;
cout << "Complex::Complex(double r, double i)" << endl;
}


Complex::~Complex()
{
cout << "Complex::~Complex()" << endl;
}

运算符重载

一种重载 “+“ 的写法:

拷贝构造与临时对象

声明:

1
2
3
4
// 运算符重载
Complex operator+ (const Complex& x);
// 拷贝构造的写法
Complex(const Complex& x);

实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 重载 + 运算符
Complex Complex::operator+ (const Complex& x)
{
Complex tmp; // tmp是一个在栈上面的临时对象
tmp._real = _real + x._real;
tmp._image = _image + x._image;
return tmp; // c++在这里会触发一个拷贝构造,传回的不是栈中的tmp对象,而是tmp的副本对象
}

// 拷贝构造的实现
Complex::Complex(const Complex& x)
{
_real = x._real;
_image = x._image;
cout << "Complex::Complex(const Complex& x)" << endl;
}

另一种重载 “+“ 的写法:

尽量避开临时变量和拷贝构造

声明:

1
2
// 运算符重载
Complex operator+ (const Complex& x);

实现:

1
2
3
4
5
// 重载 + 运算符
Complex Complex::operator+ (const Complex& x)
{
return Complex(_real + x._real, _image + x._image); // 这样可以避开临时变量和拷贝构造
}

重载 ”=“ 的写法:

声明:

1
Complex& operator= (const Complex& x);

实现:

1
2
3
4
5
6
7
// 重载 = 运算符
Complex& Complex::operator= (const Complex& x)
{
_real = x._real;
_image = x._image;
return *this;
}

临时对象与拷贝构造

栈上面的临时对象,返回的时候会触发一个拷贝构造,传回的不是栈中的对象,而是副本对象

返回的时候应该尽量避免临时对象的产生

前置后置++与–

前置++:先将变量自增,然后将值返回

后置++:先将值返回,然后将变量自增

声明:

1
2
3
// 前置和后置++
Complex& operator++ (); // 前置++
Complex operator++ (int); // 后置++

实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 前置++
Complex& Complex::operator++ ()
{
_real++;
_image++;
return *this;
}

// 后置++
Complex Complex::operator++ (int)
{
// 以对象本身作为参数,进行拷贝构造,构造出一个副本
//Complex tmp(*this);
//_real++;
//_image++;
// tmp是改变之前的对象,将tmp返回回去了,而此时this已经自增了,tmp的值等于没有自增之前的this的值
//return tmp;
}

优化:避免拷贝构造

1
2
3
4
5
6
7
8
9
10
11
12
13
// 后置++
Complex Complex::operator++ (int)
{
// 以对象本身作为参数,进行拷贝构造,构造出一个副本
//Complex tmp(*this);
//_real++;
//_image++;
// tmp是改变之前的对象,将tmp返回回去了,而此时this已经自增了,tmp的值等于没有自增之前的this的值
//return tmp;

// 不让临时变量产生,避免拷贝构造
return Complex(_real++, _image++);
}

标准输入输出IO重载

friend关键字可以让大括号之外也能访问private的变量

声明:

1
2
3
4
5
// 重载cout运算符
// friend关键字可以让大括号之外也能访问private的变量
friend ostream& operator<<(ostream& os, const Complex& x);
// 重载cin运算符
friend istream& operator>>(istream& is, Complex& x);

实现:

1
2
3
4
5
6
7
8
9
10
11
12
// 重载cout运算符
ostream& operator<<(ostream& os, const Complex& x) {
os << "real value is " << x._real << " image value is " << x._image;
return os;
}

// 重载cin运算符
istream& operator>>(istream& is, Complex& x)
{
is >> x._real >> x._image;
return is;
}

目前为止完整代码

Complex.h:

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
#pragma once

#include <iostream>
using namespace std;

class Complex
{
public:
Complex(); // 默认构造函数
Complex(double r, double i); // 构造函数
virtual ~Complex(); // 析构函数

Complex(const Complex& x); // 拷贝构造的写法

double getReal() const { return _real; }
void setReal(double d) { _real = d; }
double getImage() const { return _image; }
void setImage(double i) { _image = i; }

// 运算符重载
Complex operator+ (const Complex& x);
Complex& operator= (const Complex& x);

// 前置和后置++
Complex& operator++ (); // 前置++
Complex operator++ (int); // 后置++

// 重载cout运算符
// friend关键字可以让大括号之外也能访问private的变量
friend ostream& operator<<(ostream& os, const Complex& x);
// 重载cin运算符
friend istream& operator>>(istream& is, Complex& x);

private:
double _real; // 复数的实部
double _image; // 复数的虚部
};

Complex.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
#include "stdafx.h"
#include "Complex.h"


Complex::Complex()
{
_real = 0.0;
_image = 0.0;
// cout << "Complex::Complex()" << endl;
}

Complex::Complex(double r, double i)
{
_real = r;
_image = i;
// cout << "Complex::Complex(double r, double i)" << endl;
}


Complex::~Complex()
{
// cout << "Complex::~Complex()" << endl;
}

// 重载 + 运算符
Complex Complex::operator+ (const Complex& x)
{
// Complex tmp; // tmp是一个在栈上面的临时对象
// tmp._real = _real + x._real;
// tmp._image = _image + x._image;
// return tmp; // c++在这里会触发一个拷贝构造,传回的不是栈中的tmp对象,而是tmp的副本对象

return Complex(_real + x._real, _image + x._image); // 这样可以避开临时变量和拷贝构造
}

// 重载 = 运算符
Complex& Complex::operator= (const Complex& x)
{
_real = x._real;
_image = x._image;
return *this;
}

// 拷贝构造的实现
Complex::Complex(const Complex& x)
{
_real = x._real;
_image = x._image;
// cout << "Complex::Complex(const Complex& x)" << endl;
}

// 前置++
Complex& Complex::operator++ ()
{
_real++;
_image++;
return *this;
}

// 后置++
Complex Complex::operator++ (int)
{
// 以对象本身作为参数,进行拷贝构造,构造出一个副本
// Complex tmp(*this);
// _real++;
// _image++;
// tmp是改变之前的对象,将tmp返回回去了,而此时this已经自增了,tmp的值等于没有自增之前的this的值
// return tmp;

// 不让临时变量产生,避免拷贝构造
return Complex(_real++, _image++);
}

// 重载cout运算符
ostream& operator<<(ostream& os, const Complex& x) {
os << "real value is " << x._real << " image value is " << x._image;
return os;
}

// 重载cin运算符
istream& operator>>(istream& is, Complex& x)
{
is >> x._real >> x._image;
return is;
}

ConsoleApplication1.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
// ConsoleApplication1.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include "Complex.h"
#include <iostream>
#include <cstring>

using namespace std;

int main()
{
Complex a(3.0, 2.0);
Complex b(2.0, 3.0);
Complex c = a + b;

// 用对象构造对象,也会触发拷贝构造
Complex d(c);
Complex e;

e = d++;
cout << e << endl;
cout << d << endl;

e = ++d;
cout << e << endl;
cout << d << endl;

cin >> e;
cout << e << endl;
return 0;
}

重载+=

声明:

1
Complex& operator+= (const Complex& x);

定义:

1
2
3
4
5
6
Complex& Complex::operator+= (const Complex& x)
{
_real += x._real;
_image += x._image;
return *this;
}

重载==

声明:

1
bool operator==(const Complex& x);

定义:

1
2
3
4
bool Complex::operator==(const Complex& x)
{
return (_real == x._real && _image == x._image);
}

重载!=

声明:

1
bool operator!=(const Complex& x);

定义:

1
2
3
4
bool Complex::operator!=(const Complex& x)
{
return !(_real == x._real && _image == x._image);
}

完整代码:

Complex.h

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
#pragma once

#include <iostream>
using namespace std;

class Complex
{
public:
Complex(); // 默认构造函数
Complex(double r, double i); // 构造函数
virtual ~Complex(); // 析构函数

Complex(const Complex& x); // 拷贝构造的写法

double getReal() const { return _real; }
void setReal(double d) { _real = d; }
double getImage() const { return _image; }
void setImage(double i) { _image = i; }

// 运算符重载
Complex operator+ (const Complex& x) const;
Complex& operator= (const Complex& x);
Complex& operator+= (const Complex& x);
bool operator==(const Complex& x);
bool operator!=(const Complex& x);

// 前置和后置++
Complex& operator++ (); // 前置++
Complex operator++ (int); // 后置++

// 重载cout运算符
// friend关键字可以让大括号之外也能访问private的变量
friend ostream& operator<<(ostream& os, const Complex& x);
// 重载cin运算符
friend istream& operator>>(istream& is, Complex& x);

private:
double _real; // 复数的实部
double _image; // 复数的虚部
};

Complex.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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
#include "stdafx.h"
#include "Complex.h"


Complex::Complex()
{
_real = 0.0;
_image = 0.0;
// cout << "Complex::Complex()" << endl;
}

Complex::Complex(double r, double i)
{
_real = r;
_image = i;
// cout << "Complex::Complex(double r, double i)" << endl;
}


Complex::~Complex()
{
// cout << "Complex::~Complex()" << endl;
}

// 重载 + 运算符
Complex Complex::operator+ (const Complex& x) const
{
// Complex tmp; // tmp是一个在栈上面的临时对象
// tmp._real = _real + x._real;
// tmp._image = _image + x._image;
// return tmp; // c++在这里会触发一个拷贝构造,传回的不是栈中的tmp对象,而是tmp的副本对象

return Complex(_real + x._real, _image + x._image); // 这样可以避开临时变量和拷贝构造
}

// 重载 = 运算符
Complex& Complex::operator= (const Complex& x)
{
_real = x._real;
_image = x._image;
return *this;
}

// 重载+=
Complex& Complex::operator+= (const Complex& x)
{
_real += x._real;
_image += x._image;
return *this;
}

// 重载==
bool Complex::operator==(const Complex& x)
{
return (_real == x._real && _image == x._image);
}

// 重载!=
bool Complex::operator!=(const Complex& x)
{
return !(_real == x._real && _image == x._image);
}

// 拷贝构造的实现
Complex::Complex(const Complex& x)
{
_real = x._real;
_image = x._image;
// cout << "Complex::Complex(const Complex& x)" << endl;
}

// 前置++
Complex& Complex::operator++ ()
{
_real++;
_image++;
return *this;
}

// 后置++
Complex Complex::operator++ (int)
{
// 以对象本身作为参数,进行拷贝构造,构造出一个副本
// Complex tmp(*this);
// _real++;
// _image++;
// tmp是改变之前的对象,将tmp返回回去了,而此时this已经自增了,tmp的值等于没有自增之前的this的值
// return tmp;

// 不让临时变量产生,避免拷贝构造
return Complex(_real++, _image++);
}

// 重载cout运算符
ostream& operator<<(ostream& os, const Complex& x) {
os << "real value is " << x._real << " image value is " << x._image;
return os;
}

// 重载cin运算符
istream& operator>>(istream& is, Complex& x)
{
is >> x._real >> x._image;
return is;
}

ConsoleApplication1.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
// ConsoleApplication1.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include "Complex.h"
#include <iostream>
#include <cstring>

using namespace std;

int main()
{
Complex a(3.0, 2.0);
Complex b(2.0, 3.0);
Complex c = a + b;

// 用对象构造对象,也会触发拷贝构造
Complex d(c);
Complex e;

e = d++;
cout << e << endl;
cout << d << endl;

e = ++d;
cout << e << endl;
cout << d << endl;
cout << (e == d) << endl;
cout << (e != a) << endl;

cin >> e;
cout << e << endl;

e += a;
cout << e << endl;
return 0;
}

IO流

image-20220830233543295 image-20220830233734489

IO缓存区

image-20220830235122171
1
2
3
清空缓存区中的脏数据cin.ignore(要清空缓存区的字节数,以什么符号结尾)
numeric_limits<std::streamsize>::max()为缓存区的最大范围
cin.ignore(numeric_limits<std::streamsize>::max(), '\n');
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 <iostream>
using namespace std;

int main()
{
int a;
int index = 0;
while (cin >> a)
{
cout << "the numbers are: " << a << endl;
index++;
if (index == 5)
{
break;
}
}

// 清空缓存区中的脏数据cin.ignore(要清空缓存区的字节数,以什么符号结尾)
// numeric_limits<std::streamsize>::max()为缓存区的最大范围
cin.ignore(numeric_limits<std::streamsize>::max(), '\n');

char ch;
cin >> ch;
cout << "the last char is: " << ch << endl;

return 0;
}

文件操作

image-20220905100309842

image-20220905100755559

文件打开方式

image-20220905101927314

以默认(覆盖)的方式打开

1
2
// 打开文件流,以默认(覆盖)的方式打开
fout.open("testBuffer.txt");

open()方法,默认是以ios::in和ios::out的方式打开

如果文件不存在,不会自动帮我们创建一个文件

写入不是一种追加的方式,而是会覆盖原有内容

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
// demo0.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <iostream>
#include <fstream>
using namespace std;

int main()
{
int a;
int index = 0;

// 创建文件流
fstream fout;
// 打开文件流,以默认(覆盖)的方式打开
fout.open("testBuffer.txt");

// 检测文件是否打开失败,如果文件不存在,就会打开失败,输出"open file failed"
if (fout.fail())
{
cout << "open file failed" << endl;
}

while (cin >> a)
{
// 写入文件流
fout << "The numbers are: " << a << endl;
index++;
if (index == 5)
{
break;
}
}

cin.ignore(numeric_limits<std::streamsize>::max(), '\n');

char ch;
cin >> ch;
// 写入文件流
fout << "The last char is: " << ch << endl;

// 关闭文件流
fout.close();
return 0;
}

检测文件是否打开失败

直接看是否打开失败:

1
2
3
4
5
// 检测文件是否打开失败,如果文件不存在,就会打开失败,输出"open file failed"
if (fout.fail())
{
cout << "open file failed" << endl;
}

也可以看文件流对象是否存在:如果文件不存在,也会打开失败

1
2
3
4
if (!fout)
{
cout << "open file failed" << endl;
}

以追加的方式打开

在文件的尾部添加数据

如果文件不存在,会自动创建一个文件

1
2
// 打开文件流,以追加的方式打开
fout.open("testBuffer.txt", ios::app);
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
// demo0.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <iostream>
#include <fstream>
using namespace std;

int main()
{
int a;
int index = 0;

// 创建文件流
fstream fout;
// 打开文件流,以追加的方式打开
fout.open("testBuffer.txt", ios::app);

if (!fout)
{
cout << "open file failed" << endl;
}

while (cin >> a)
{
// 写入文件流
fout << "The numbers are: " << a << endl;
index++;
if (index == 5)
{
break;
}
}

cin.ignore(numeric_limits<std::streamsize>::max(), '\n');

char ch;
cin >> ch;
// 写入文件流
fout << "The last char is: " << ch << endl;

// 关闭文件流
fout.close();
return 0;
}

操作二进制文件

以拷贝一个MP3文件为例

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
// demo0.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <iostream>
#include <fstream>
using namespace std;

static const int bufferLen = 2048;

// src为原文件名,dst为目标文件名
bool CopyFile(const string& src, const string& dst)
{
// 打开源文件和目标文件
// 源文件以二进制读的方式打开
ifstream in(src.c_str(), ios::in | ios::binary);

// 目标文件以二进制写的方式打开,以覆盖的方式写
ofstream out(dst.c_str(), ios::out | ios::binary | ios::trunc);

// 判断文件打开是否成功
if (!in || !out)
{
return false;
}

// temp是缓存,最大2048字节
char temp[bufferLen];
// 从源文件中读取数据,写到目标文件中
// 通过读取源文件的EOF来判断读写是否结束
while (!in.eof())
{
// read(把文件读到哪里, 每一次最多读取多少字节)
in.read(temp, bufferLen);

// 从最后一次读取中,得到实际获取的数量
streamsize count = in.gcount();

// write(写入的内容,写入的长度)
out.write(temp, count);
}

// 关闭源文件和目标文件
in.close();
out.close();

return true;
}

int main()
{
cout << CopyFile("testcopyfile.mp3", "testcopyfile2.mp3") << endl;
return 0;
}

操作结果:成功

image-20220905143305413

头文件重复包含的问题

image-20220905150807613

头文件可以全部放到stdafx.h文件中,防止头文件太乱:

image-20220905152058090

深拷贝与浅拷贝

image-20220905155657717

设计String

设计String,实现拷贝构造和赋值运算符为深拷贝;并且设计String的移动构造函数和移动赋值运算符,使其满足移动语义

stdafx.h

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

#include "targetver.h"

#include <stdio.h>
#include <tchar.h>

// TODO: 在此处引用程序需要的其他头文件
#include <cstdlib>
#include <string>
#include <iostream>
using namespace std;
#include "String.h"

String.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#pragma once
class String
{
public:
String(const char *str = NULL); // 普通构造函数
String(const String &other); // 拷贝构造函数
String(String&& other); // 移动构造函数
~String(void); // 析构函数
String& operator= (const String& other); // 赋值函数
String& operator=(String&& rhs)noexcept; // 移动赋值运算符

friend ostream& operator<<(ostream& os, const String &c); // cout输出

private:
char *m_data; // 用于保存字符串
};

String.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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
#include "stdafx.h"


// _CRT_SECURE_NO_WARNINGS

// String 的普通构造函数
String::String(const char *str)
{
if (str == NULL)
{
m_data = new char[1];
if (m_data != NULL)
{
*m_data = '\0';
}
else
{
exit(-1);
}
}
else
{
int len = strlen(str);
m_data = new char[len + 1];
if (m_data != NULL)
{
strcpy(m_data, str);
}
else
{
exit(-1);
}
}
}

// 拷贝构造函数
String::String(const String &other)
{
int len = strlen(other.m_data);
m_data = new char[len + 1];
if (m_data != NULL)
{
strcpy(m_data, other.m_data);
}
else
{
exit(-1);
}
}

// 移动构造函数
String::String(String&& other)
{
if (other.m_data != NULL)
{
// 资源让渡
m_data = other.m_data;
other.m_data = NULL;
}
}

// 赋值函数
String& String::operator= (const String &other)
{
if (this == &other)
{
return *this;
}
// 释放原有的内容
delete[] m_data;
// 重新分配资源并赋值
int len = strlen(other.m_data);
m_data = new char[len + 1];
if (m_data != NULL)
{
strcpy(m_data, other.m_data);
}
else
{
exit(-1);
}

return *this;
}

// 移动赋值运算符
String& String::operator=(String&& rhs)noexcept
{
if (this != &rhs)
{
delete[] m_data;
m_data = rhs.m_data;
rhs.m_data = NULL;
}
return *this;
}

// String 的析构函数
String::~String(void)
{
if (m_data != NULL)
{
delete[] m_data;
}
}

ostream& operator<<(ostream& os, const String &c)
{
os << c.m_data;
return os;
}

demo.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include "stdafx.h"

int main()
{
String s1("Hello"); // 构造函数
cout << s1 << endl;
//String s2 = s1; // 调用拷贝构造函数
String s2(s1); // 调用拷贝构造函数
cout << s2 << endl;
String s2A(std::move(s1)); // 移动构造函数
cout << s2A << endl;
String s3; // 无参构造函数
cout << s3 << endl;
s3 = s2; // 调用赋值函数
cout << s3 << endl;
String s3A; // 无参构造函数
s3A = std::move(s2A); // 移动赋值运算符
cout << s3A << endl;

return 0;
}

移动语义

image-20220905164608492

移动语义:设计在String的移动构造函数和移动赋值运算符中

面向对象(继承与多态)

子类方法和父类实现不一样,则要在父类的这个方法上加上vitual

子类的对象里面保存了什么?

子类的对象里面保存了什么?

  1. 自己的变量
  2. 父类的变量
  3. 还有一个指向虚表的指针(4个字节),虚表里面包含了父类虚函数的地址

注意:对象模型中,不保存一般的函数地址,只保留成员变量的信息和虚函数的虚表

this指针底层原理

一般函数能够引用到成员变量的变量信息和虚函数的信息,是因为一般函数能找到对象的地址

一般函数是通过this指针找到的对象地址,this指针指向对象本身

this指针很多时候就是通过ecx这个寄存器进行传递的

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
// demo1.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include<iostream>
using namespace std;

// 抽象类
class Shape
{
public:
// 子类方法和父类实现不一样,则要在父类的这个方法上加上vitual
// 常规实现
// virtual double Area() const { return 0; }

// 可以不实现,给他一个虚的实现,= 0的意思是这个类不可以有实体的对象,因为这个类是一个虚类
// 当看到virtual并且函数的方法等于0的时候,说明这个类是抽象类
virtual double Area() const = 0;

void Display()
{
cout << Area() << endl;
}
};

class Square:public Shape
{
public:
// 构造函数中,这也是一种成员变量的赋值方法,使用成员变量的参数列表来初始化
Square(double len):_len(len){}

double Area() const
{
return _len * _len;
}

private:
double _len;
};

class Circle:public Shape
{
public:
Circle(double radius):_radius(radius){}

double Area() const
{
return 3.1415926 * _radius * _radius;
}

private:
double _radius;
};

class Triangle :public Shape
{
public:
Triangle(double len, double height):_len(len), _height(height){}

double Area() const
{
return 0.5 * _len * _height;
}

private:
double _len;
double _height;
};

int main()
{
Square s1(2.0);
Circle c1(2.0);
Triangle t1(2.0, 3.0);

Shape* shapes[3];
shapes[0] = &s1;
shapes[1] = &c1;
shapes[2] = &t1;

for (unsigned int index = 0; index < 3; index++)
{
shapes[index]->Display();
}
return 0;
}

C++编程思想

单例模式

实现思路:

Singleton拥有一个私有构造函数,确保用户无法通过new直接实例它

包含一个静态私有成员变量instance与静态公有方法instance()

Singleton.h

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

using namespace std;

class Singleton
{
public:
static const Singleton* getInstance();
static void DoSomething()
{
cout << "Do Something" << endl;
}

// 构造和析构函数私有化,防止外部访问
private:
Singleton();
~Singleton();

// 可见范围是私有的,在类的内部可见
// 但是static静态变量的生命周期在全局区域,随着程序的产生而产生,随着程序的灭亡而灭亡
// 静态变量帮助解决资源的分配和释放
static Singleton* This;
};

Singleton.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include "stdafx.h"
#include "Singleton.h"

// Singleton* Singleton::This = new Singleton(); // 饿汉单例模式
Singleton* Singleton::This = NULL; // 懒汉单例模式

const Singleton* Singleton::getInstance()
{
if (!This)
{
This = new Singleton();
}
return This;
}

Singleton::Singleton()
{
}

Singleton::~Singleton()
{
}

demo.cpp

1
2
3
4
5
6
7
8
9
10
11
// demo1.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include "Singleton.h"

int main()
{
Singleton::getInstance()->DoSomething();
return 0;
}

观察者模式

在观察者模式中,观察者需要直接订阅目标事件;在目标发出内容改变的事件后,直接接收事件并作出响应,对象常是一对多关系

常见场景:各种MVC的框架中,Model的变化通知各种类型的View时几乎都存在这种模式

实现思路:将问题的职责解耦合,将Observable和Observer抽象开,分清抽象和实体

-------------本文结束感谢您的阅读-------------