一直都有在有接触C++,但是一直都没有系统的学过,虽然语法上和C很类似,但是学了以后才知道,还是有挺大不同,面向对象真的挺方便的。
为什么学C++了,因为现在用C++写一套dxf的插件时候,用vector
写地图数据时候,出现 expression vector subscript out of range
的报错,但是一直不知道怎么解决。
封装
结构体的认识
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 <stdlib.h> #include<stdio.h> #include<windows.h>
struct Student{ int a; int b; int c; int d; };
int Plus(Student s) { return s.a + s.b + s.c + s.d; }
int main() { Student s = { 1,2,3,4 }; int r = Plus(s);
return 0; }
|
这段代码就是把四个值,传过去相加,程序可以完美运行,没有报错。

但是通过反汇编的角度去看一下代码。

观察一下参数传递调用的那里,按照正常的堆栈传递,这里应该是 Push 开头,但是这里不同
先通过 sub esp,10h
先通过esp 提升 10个字节,10进制就是 4*4 16,他先提升了16个字节,然后把
[ebp-10h]
[ebp-0Ch]
[ebp-8]
[s]
分别传递,这四个ebp - 是什么东西,其实就是上面 Student s = {1,2,3,4}的四个值,分别通过参数传递的方式传进结构体里,只是这里没用push 的方法,而是通过 内存复制的方式 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| int r = Plus(s); 00134324 sub esp,10h 00134327 mov eax,esp 00134329 mov ecx,dword ptr [s] 0013432C mov dword ptr [eax],ecx 0013432E mov edx,dword ptr [ebp-10h] 00134331 mov dword ptr [eax+4],edx 00134334 mov ecx,dword ptr [ebp-0Ch] 00134337 mov dword ptr [eax+8],ecx 0013433A mov edx,dword ptr [ebp-8] 0013433D mov dword ptr [eax+0Ch],edx 00134340 call Plus (013123Fh) 00134345 add esp,10h 00134348 mov dword ptr [r],eax
|
为什么结构体会采用这种方式,因为编辑器,默认是不会知道你结构体有多少个参数,每个参数是什么类型,所以要传递参数时,编辑器会在堆栈中先分出一块空间,然后在通过寄存器在局部变量里面一个一个的复制进去。
虽然可以正常达到参数传递的目的,但是如果要传的参数过多时,这样子就会造成大量的内存复制,其实不太好,可以通过指针传递的方式达到相同的目的。
结构体指针传递参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| #include <iostream> #include <stdlib.h> #include<stdio.h> #include<windows.h>
struct Student{ int a; int b; int c; int d; };
int Plus(Student* s) { return s->a + s->b + s->c + s->d; }
int main() { Student s = { 1,2,3,4 }; int r = Plus(&s); printf("%d\n", r); return 0; }
|
运行,查看反汇编,看下代码。

看下参数传递那里
1 2 3 4 5 6 7 8 9 10 11 12
| Student s = { 1,2,3,4 }; 010A4312 mov dword ptr [s],1 010A4319 mov dword ptr [ebp-14h],2 010A4320 mov dword ptr [ebp-10h],3 010A4327 mov dword ptr [ebp-0Ch],4
int r = Plus(&s); 010A432E lea eax,[s] 010A4331 push eax 010A4332 call Plus (010A1393h) 010A4337 add esp,4 010A433A mov dword ptr [r],eax
|
这里变得非常的简单,首先取一下当前参数的首地址,然后把首地址传给当前的函数,这样子一比较,这么写性能肯定比内存复制要好很多。
到函数里面看一看,也没有发生太大变化,先取出第一个值,然后传给eax,在把第一个值传过去在+4,取第二个值,以此类推。

以后结构体传递参数时,尽量传递结构体的指针,避免大量的内存复制。
C++与C的不同,编译器替我们做的事情
虽然指针传递这样子写,是方便了很多,但是如果参数多了,写起来也很痛苦,所以改写一下代码,展示出C++的优势,就是编译器替我们做重复的事情
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| #include <iostream> #include <stdlib.h> #include<stdio.h> #include<windows.h>
struct Student{ int a; int b; int c; int d; int Plus() { return a+b+c+d; } };
int main() { Student s = { 1,2,3,4 }; int r = s.Plus(); printf("%d\n", r); return 0; }
|
这样子,结果还是一样是10,但是不用在痛苦的去写 s->a 这样子了。
把函数放进结构体里面,这样子编译器就可以替你做这些事情。从汇编代码看

1 2 3 4 5 6 7 8 9
| Student s = { 1,2,3,4 }; 00F14312 mov dword ptr [s],1 00F14319 mov dword ptr [ebp-14h],2 00F14320 mov dword ptr [ebp-10h],3 00F14327 mov dword ptr [ebp-0Ch],4 int r = s.Plus(); 00F1432E lea ecx,[s] 00F14331 call Student::Plus (0F11398h) 00F14336 mov dword ptr [r],eax
|
这里和刚才的很类似,但是我们没有传参数进去,但是实际上是传,我们没有写,为什么会传了,因为我们把函数写进了结构体里面,编译器就知道了,然后就替我们做了这一步。我们把当前函数的指针传进去,我们只要告诉编译器我们要使用的是哪一个结构体的函数。
类
这样子也叫类,就是带有函数的结构体
1 2 3 4 5 6 7 8 9 10
| struct Student{ int a; int b; int c; int d; int Plus() { return a+b+c+d; } };
|
成员函数
结构体里面的函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| #include <iostream> #include <stdlib.h> #include<stdio.h> #include<windows.h>
struct Student{ int a; int b; int c; int d; int Plus() { return a+b+c+d; } };
int main() { printf("%d\n", sizeof(Student)); 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
| #include <iostream> #include <stdlib.h> #include<stdio.h> #include<windows.h>
struct Student{ int a; int b; int c; int d; int e; int Plus() { return a+b+c+d; } };
int main() { printf("%d\n", sizeof(Student)); return 0; }
|

可以看到,结构体大小和成员数量有关,一个int类型占4个字节。
封装总结
This 指针
通常情况下,编译器都会采用ECX
来传递一个结构体的指针,也可以用来识别代码是C写的还是C++写的。
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
| #include <iostream> #include <stdlib.h> #include<stdio.h> #include<windows.h>
struct Student{ int a; int b; int c; int d; void init(int a, int b, int c, int d) { a = a; b = b; c = c; d = d; } };
int main() { Student s ; s.init(1,2,3,4); return 0; }
|
通常情况下,代码这么写,作为人的话,当然知道。就是函数init
四个变量的值等于结构体中的成员a b c d
但是编译器默认是不知道的,查看反汇编代码就清楚了。

可以看到通过push 传入参数。

但是在结构体内部,就出了问题,我们的目的是把结构体成员赋值给函数参数,但是从汇编代码看,是参数自身传值给自身,为什么会这样,因为编译器是不知道你的a是哪个,所以他就会把参数自身的a来调用,这时候就要告诉他,就要使用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
| #include <iostream> #include <stdlib.h> #include<stdio.h> #include<windows.h>
struct Student{ int a; int b; int c; int d; void init(int a, int b, int c, int d) { this->a = a; this->b = b; this->c = c; this->d = d; } };
int main() { Student s ; s.init(1,2,3,4); return 0; }
|

1 2 3 4 5 6
| 01291830 mov dword ptr [this],ecx
this->a = a; 0129183D mov eax,dword ptr [this] 01291840 mov ecx,dword ptr [a] 01291843 mov dword ptr [eax],ecx
|
汇编代码角度很清楚,先把ecx
取出来,ecx
传给this
指针
在把 [this]
指针传给eax
,把[a]
传给ecx
,在把 ecx
的值传给[eax]
eax
= this->a
ecx
= 成员a
This总结
This
指针不能做运算,他的含义只有一个,就是当前结构体首地址,对他进行运算,赋值都不行。
This
指针是编译器默认传入的,通常都会使用ecx
进行参数的传递
This
指针不能做++ — 等运算,不能重新被赋值
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
|
#include <iostream> #include <stdlib.h> #include<stdio.h> #include<windows.h>
struct Student{ int a; int b; int c; int d; Student() { printf("Hello"); } };
int main() { Student s; return 0; }
|

可以看到,我们没有调用那个函数只是创建了一个对象,他自己执行输出了Hello。
看下汇编代码。编译器自动调用了构造函数。

有参构造函数
构造函数可以随意定义参数,但是创建对象时,需要把参数带上,不要编译器会报错。
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
| #include <iostream> #include <stdlib.h> #include<stdio.h> #include<windows.h>
struct Student{ int a; int b; int c; int d; Student(int a,int b,int c,int d) { this->a = a; this->b = b; this->c = c; this->d = d; printf("Hello"); } };
int main() { Student s(1,2,3,4); 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
| #include <iostream> #include <stdlib.h> #include<stdio.h> #include<windows.h>
struct Student{ int a; int b; int c; int d; Student() { printf("无参构造函数\n"); } Student(int a,int b,int c,int d) { this->a = a; this->b = b; this->c = c; this->d = d; printf("有参构造函数\n"); } };
int main() { Student a; Student s(1,2,3,4); 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
|
#include <iostream> #include <stdlib.h> #include<stdio.h> #include<windows.h>
struct Student{ int a; int b; int c; int d; Student() { printf("无参构造函数\n"); } Student(int a,int b,int c,int d) { this->a = a; this->b = b; this->c = c; this->d = d; printf("有参构造函数执行\n"); } ~Student() { printf("析构函数执行\n"); } };
int main() { Student s(1,2,3,4); return 0; }
|
析构函数,通常是在对象被用完了,不要了时候执行。
代码中 创建了对象 s
,传入了四个参数,首先会执行有参构造函数
因为是在main函数中创建的对象,就是堆栈中,当main
函数执行完毕,析构函数就会执行,通过汇编代码来看。


1 2 3 4 5 6 7 8 9 10 11 12 13
| Student s(1,2,3,4); 00AA1A52 push 4 00AA1A54 push 3 00AA1A56 push 2 00AA1A58 push 1 00AA1A5A lea ecx,[s] 00AA1A5D call Student::Student (0AA10A0h) return 0; 00AA1A62 mov dword ptr [ebp-0E4h],0 00AA1A6C lea ecx,[s] 00AA1A6F call Student::~Student (0AA122Bh) 00AA1A74 mov eax,dword ptr [ebp-0E4h] }
|
可以看到,创建对象,然后调用有参构造函数。 当代码走到return 0
时,调用了析构函数 ~Student
。
因为通常析构函数是在main
函数执行完毕时候执行,所以如果不下断点,控制台是看不见的,所以如果要看到他执行效果,可以在析构函数处下个断点,或者在return 0
处下个断点。

如果创建对象是全局变量,那么析构函数会在进程结束时候执行。
析构函数何时执行
- 当对象在堆栈中分配
- 当对象在全局区分配
析构函数总结
只能有一个析构函数,不能重载
不能带任何参数
不能带返回值
主要用于清理工作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| struct Person { int age; int level; char* arr;
Person(int age, int level) { this->age = age; this->level = level; arr = (char*)malloc(1024); } ~Person() { printf("析构函数执行\n"); free(arr); } };
|
继承
继承就是数据的复制
子类可以使用父类定义的变量,这样子就可以减少重复代码的编写。
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 <stdlib.h> #include<stdio.h> #include<windows.h>
struct Person{ int age; int sex; };
struct Teacher:Person { int level; int classId; };
int main() { Teacher t; t.age = 1; t.sex = 2; t.classId = 3; t.level = 4; 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
| #include <iostream> #include <stdlib.h> #include<stdio.h> #include<windows.h>
struct Person{ int age; int sex; };
struct Teacher:Person { int classId; };
int main() { Teacher t;
t.age = 1; t.sex = 2; t.classId = 3; printf("%d", sizeof(t)); return 0; }
|
现在看下当前 Teachear
的大小,等于 12,现在只有三个变量,一个占4个字节。

如果子类中有父类相同的变量,例如又定义了一个 int age
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 <iostream> #include <stdlib.h> #include<stdio.h> #include<windows.h>
struct Person{ int age; int sex; };
struct Teacher:Person { int age; int classId; };
int main() { Teacher t;
t.age = 1; t.sex = 2; t.classId = 3; printf("%d", sizeof(t)); return 0; }
|
16,多了一个重复的变量,但是编译器不管你是否是重复的,他只会将你定义的两个变量加上去,进行重写。

访问子类或父类
因为有重复的变量名,如果要访问父类的变量时就应该告诉编译器
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
| #include <iostream> #include <stdlib.h> #include<stdio.h> #include<windows.h>
struct Person{ int age; int sex; };
struct Teacher:Person { int age; int classId; };
int main() { Teacher t;
t.age = 1; t.sex = 2; t.classId = 3; t.Person::age = 4; 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
| #include <iostream> #include <stdlib.h> #include<stdio.h> #include<windows.h>
struct Person{ int age; int sex; };
struct Teacher:Person { int level; int classId; }; struct student :Teacher { int name; int number; };
int main() { student s; return 0; }
|
这种时候,s
这个对象,不仅能访问 Teacher
这个父类,还能访问 Teacher
的父类,或者说他爷爷类,Person
。


多重继承总结
多重继承增加了程序的复杂度,不建议使用
继承总结
- 继承就是数据的复制
- 减少重复代码的编写
- Person 称为父类或者基类
- Teacher 称为子类或者派生类
堆中创建对象
创建对象
<1>全局变量区
<2>栈
1 2 3 4
| void Max() { Person p; }
|
<3>堆:new delete
1 2 3 4
| 在堆中创建对象: Person* p = new Person(); 释放对象占用的内存: delte p;
|
在C语言中,要在堆中创建对象, 可以使用malloc()
申请一块空间,在C++中,使用new
;
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
|
#include <iostream> #include <stdlib.h> #include<stdio.h> #include<windows.h>
class Person { private: int x; int y; public: Person() { printf("Person()执行了 \n"); } Person(int x, int y) { printf("Person(int x,int y)执行了 \n"); this->x = x; this->y = y; } ~Person() { printf("~Person()执行了\n"); } };
int main() { Person* p = new Person(1, 2);
delete p;
return 0; }
|
执行流程,当执行了new 以后,会执行有参构造

当执行完delete 时候,就会执行析构函数。

C 和C++在堆中创建对象的不同
C
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
|
#include <iostream> #include <stdlib.h> #include<stdio.h> #include<windows.h>
class Person { private: int x; int y; public: Person() { printf("Person()执行了 \n"); } Person(int x, int y) { printf("Person(int x,int y)执行了 \n"); this->x = x; this->y = y; } ~Person() { printf("~Person()执行了\n"); } };
int main() { Person* p = (Person*)malloc(sizeof(Person) * 10);
delete p; return 0; }
|
执行代码看效果,可以很清晰的看到,当用C 通过 malloc 申请一块空间时,不会执行构造函数,只在 使用了 delete以后,会执行析构函数。

但是通常,C语言使用malloc
申请空间时候,是使用 free
函数清理空间
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 <iostream> #include <stdlib.h> #include<stdio.h> #include<windows.h>
class Person { private: int x; int y; public: Person() { printf("Person()执行了 \n"); } Person(int x, int y) { printf("Person(int x,int y)执行了 \n"); this->x = x; this->y = y; } ~Person() { printf("~Person()执行了\n"); } };
int main() { Person* p = (Person*)malloc(sizeof(Person) * 10); free(p); return 0; }
|
当使用free
函数清理空间时候,也不会调用析构函数

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
|
#include <iostream> #include <stdlib.h> #include<stdio.h> #include<windows.h>
class Person { private: int x; int y; public: Person() { printf("Person()执行了 \n"); } Person(int x, int y) { printf("Person(int x,int y)执行了 \n"); this->x = x; this->y = y; } ~Person() { printf("~Person()执行了\n"); } };
int main() { Person* p = new Person[10];
delete p; return 0; }
|
执行代码看效果,可以清晰的看到,这里在堆中创建了10个对象,并且每次创建对象时候都会为对象调用一次构造函数。
,
因为我们在这里申请了10个对象,所以delete 也要使用[]
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
|
#include <iostream> #include <stdlib.h> #include<stdio.h> #include<windows.h>
class Person { private: int x; int y; public: Person() { printf("Person()执行了 \n"); } Person(int x, int y) { printf("Person(int x,int y)执行了 \n"); this->x = x; this->y = y; } ~Person() { printf("~Person()执行了\n"); } };
int main() { Person* p = new Person[10]; delete[] p; return 0; }
|

如果申请了多个对象时,使用了delete
而不是 delete[]
,就会报错。
堆中创建对象总结
- 使用new 时,用delete 清理
- 使用new []时,用delete[]清理
引用
&
引用类型必须初始化,引用就是起别名
小例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| #include <iostream> #include <stdlib.h> #include<stdio.h> #include<windows.h>
int main() { int x = 10; int& ref = x;
ref = 20; printf("%d\n", x); return 0; }
|
x
的值为20 而不是一开始的10。

其他类型
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 <iostream> #include <stdlib.h> #include<stdio.h> #include<windows.h>
class Base { public: int x; };
int main() { int x = 10; int& ref = x; ref = 20; printf("%d\n", x);
Base b; b.x = 1; Base& ref = b; ref.x = 2; printf("%d\n", b.x);
int* x = (int*)1; int*& ref = x; ref = (int*)2; printf("%d\n", x);
int arr[] = { 1,2,3 }; int(&p)[3] = arr; p[0] = 4; printf("%d\n", arr[0]); return 0; }
|
为什么起了一个别名,并且对他重新赋值以后,也会把原来的值一并修改了?通过汇编代码来看看

1 2 3 4 5 6
| int& ref = x; 00B054B9 lea eax,[x] 00B054BC mov dword ptr [ref],eax ref = 20; 00B054BF mov eax,dword ptr [ref] 00B054C2 mov dword ptr [eax],14h
|
首先,取[x]
的地址,给eax
,接着把该地址 传给[ref]
,当对ref
赋值时候,因为现在ref
的地址是 x
的地址,所以对ref
重新赋值,就相当于对 x
赋值。
把代码改成指针
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
| #include <iostream> #include <stdlib.h> #include<stdio.h> #include<windows.h>
class Base { public: int x; };
int main() { int x = 10; int* ref = &x; *ref = 20; printf("%d\n", x);
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
|
#include <iostream> #include <stdlib.h> #include<stdio.h> #include<windows.h>
class Base { public: int x; };
int main() { int x = 1;
int* p = &x; int& ref = x; p++; ref++; p = (int*)1; ref = 100;
return 0; }
|
从汇编代码角度来看

声明
可以看,这里没有任何区别,相当于两个指针,在往下看运算。
1 2 3 4 5 6
| int* p = &x; 010117D9 lea eax,[x] 010117DC mov dword ptr [p],eax int& ref = x; 010117DF lea eax,[x] 010117E2 mov dword ptr [ref],eax
|
运算
指针p
的 ++,首先他先取了p
的值,[p]
相当于取p的值给eax
,然后给eax
+4,接着在把eax
传回给p
的值
1 2 3 4 5 6 7 8 9 10 11
| p++; 010117E5 mov eax,dword ptr [p] 010117E8 add eax,4 010117EB mov dword ptr [p],eax ref++; 010117EE mov eax,dword ptr [ref] 010117F1 mov ecx,dword ptr [eax] 010117F3 add ecx,1 010117F6 mov edx,dword ptr [ref] 010117F9 mov dword ptr [edx],ecx
|
引用的ref
做++时候,可以看,他先把ref
的值取出来给了eax
,然后在把eax
的值又取出来给了ecx
,对应的这个值,其实就是 x
的 1。把1进行+ 1,然后就是 把[ref]
的值传给edx
,在把自增完的ecx
传给[edx]
的值。
当对指针做运算,就是相当于对指针本身做运算,指针里面的值自己说了算
当对引用做运算,就是相当于对x++做运算,引用就是一个别名。
赋值
1 2 3 4 5 6
| p = (int*)1; 010117FB mov dword ptr [p],1 ref = 100; 01011802 mov eax,dword ptr [ref] 01011805 mov dword ptr [eax],64h
|
这里就很明显了,指针[p]
本身可以做赋值,当对 引用ref
做赋值时候,就要先把[ref]
的值取出来,给eax
,赋值完,在传回给[eax]
的值。其实就是相当于改x
。
对类进行操作
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
|
#include <iostream> #include <stdlib.h> #include<stdio.h> #include<windows.h>
class Base { public: int x; int y; Base(int x, int y) { this->x = x; this->y = y; } };
int main() { Base b(1,2);
Base* p = &b; Base& ref = b; p++; ref++; p = (Base*)1; ref = 100;
return 0; }
|
初始声明和上面是一样的。
但是运算,p++
是肯定可以的,ref++
是不可以的。为什么?
因为 ref
等于 b
,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 35 36 37 38
|
#include <iostream> #include <stdlib.h> #include<stdio.h> #include<windows.h>
class Person { public: int Age; int Sex; void Work() { printf("Person:Work()"); } }; class Teacher :public Person { public: int Level; };
int main() { Teacher t; t.Age = 1; t.Sex = 2; t.Level = 3;
t.Work();
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
|
#include <iostream> #include <stdlib.h> #include<stdio.h> #include<windows.h>
class Person { public: int Age; int Sex; void Work() { printf("Person:Work()"); } }; class Teacher :public Person { public: int Level; };
int main() { Teacher t; t.Age = -1; t.Sex = 2; t.Level = 3;
t.Work();
return 0; }
|
定义了一个人,人的岁数是不可能是-1
岁的。但是他是int
类型,所以他是合法的,但是不合理。
所以不能把成员直接暴露出来。就像电脑主机,没可能直接裸露主板出来,通常都会装一个机箱把它包起来。
设计思路
一种好的设计思路,一般不希望给别人访问的,就把它包起来。
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 <iostream> #include <stdlib.h> #include<stdio.h> #include<windows.h>
class Person { private: int Age; int Sex; public: void Work() { printf("Person:Work()"); } void SetAge(int Age) { if (Age < 0) { this->Age = 0; } else { this->Age = Age; } } void SetSex(int Sex) { this->Sex = Sex; } };
class Teacher :public Person { private: int Level; public: void SetLevel(int Level) { this->Level = Level; } };
int main() { Teacher t; t.SetAge(1); t.SetSex(2); t.SetLevel(3); t.Work();
return 0; }
|
为什么要隐藏数据成员
《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 65 66
|
#include <iostream> #include <stdlib.h> #include<stdio.h> #include<windows.h>
class Person { private: int Age; int Sex; public: void Work() { printf("Person:Work()"); } void SetAge(int Age) { if (Age < 0) { this->Age = 0; } else { this->Age = Age; } } void SetSex(int Sex) { this->Sex = Sex; } };
class Teacher :public Person { private: int Level; public: Teacher() { } Teacher(int Level) { this->Level = Level; } void SetLevel(int Level) { this->Level = Level; } };
int main() { Teacher t(1); t.Work();
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
|
#include <iostream> #include <stdlib.h> #include<stdio.h> #include<windows.h>
class Person { private: int Age; int Sex; public: Person() {
} Person(int Age, int Sex) { this->Age = Age; this->Sex = Sex; } void Work() { printf("Person:Work()"); } void SetAge(int Age) { if (Age < 0) { this->Age = 0; } else { this->Age = Age; } } void SetSex(int Sex) { this->Sex = Sex; } };
class Teacher :public Person { private: int Level; public: Teacher() {
} Teacher(int Level) { this->Level = Level; } void SetLevel(int Level) { this->Level = Level; } };
int main() { Teacher t(1); t.Work();
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
|
#include <iostream> #include <stdlib.h> #include<stdio.h> #include<windows.h>
class Person { private: int Age; int Sex; public: Person() {
} Person(int Age, int Sex) { this->Age = Age; this->Sex = Sex; } void Work() { printf("Person:Work()"); } void SetAge(int Age) { if (Age < 0) { this->Age = 0; } else { this->Age = Age; } } void SetSex(int Sex) { this->Sex = Sex; } };
class Teacher :public Person { private: int Level; public: Teacher() {
} Teacher(int Age,int Sex,int Level):Person(Age,Sex) { this->Level = Level; } void SetLevel(int Level) { this->Level = Level; } };
int main() { Teacher t(1,2,3); t.Work();
return 0; }
|
面向对象设计过程封装与继承总结
- 当不希望被外人访问的成员时,使用私有化
private
把它保护起来
- 子类使用构造函数时,父类有有参构造函数时,必须提供无参构造
多态
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
|
#include <iostream> #include <stdlib.h> #include<stdio.h> #include<windows.h>
class Person { private: int Age; int Sex; public: Person() {
} Person(int Age, int Sex) { this->Age = Age; this->Sex = Sex; } void Work() { printf("Person:Work()"); } void SetAge(int Age) { if (Age < 0) { this->Age = 0; } else { this->Age = Age; } } void SetSex(int Sex) { this->Sex = Sex; } void print() { printf("Age:%d,Sex%d\n", Age, Sex); } };
class Teacher :public Person { private: int Level; public: Teacher() {
} Teacher(int Age,int Sex,int Level):Person(Age,Sex) { this->Level = Level; } void SetLevel(int Level) { this->Level = Level; } }; void printPerson(Person &p) { p.print(); } void printTeacher(Teacher &t) { t.print(); }
int main() { Person p(1, 2); Teacher t(1, 2, 3);
printPerson(p); printTeacher(t); Person* px = &p; Teacher* tx = &t;
return 0; }
|
输出结果没有问题,就是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 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 <iostream> #include <stdlib.h> #include<stdio.h> #include<windows.h>
class Person { private: int Age; int Sex; public: Person() {
} Person(int Age, int Sex) { this->Age = Age; this->Sex = Sex; } void Work() { printf("Person:Work()"); } void SetAge(int Age) { if (Age < 0) { this->Age = 0; } else { this->Age = Age; } } void SetSex(int Sex) { this->Sex = Sex; } void print() { printf("Age:%d,Sex%d\n", Age, Sex); } };
class Teacher :public Person { private: int Level; public: Teacher() {
} Teacher(int Age,int Sex,int Level):Person(Age,Sex) { this->Level = Level; } void SetLevel(int Level) { this->Level = Level; } };
void printPerson(Person &p) { p.print(); }
int main() { Person p(1, 2); Teacher t(1, 2, 3); printPerson(t);
return 0; }
|

因为 int Age
int Sex
是从父类中继承来的,所以父类可以使用指针,指向子类对象访问自己继承下来的两个成员,但是不能访问子类的成员 Level
,所以输出的值只能是Age,Sex
。

函数重写
要有统一的接口
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
|
#include <iostream> #include <stdlib.h> #include<stdio.h> #include<windows.h>
class Person { private: int Age; int Sex; public: Person() {
} Person(int Age, int Sex) { this->Age = Age; this->Sex = Sex; } void Work() { printf("Person:Work()"); } void SetAge(int Age) { if (Age < 0) { this->Age = 0; } else { this->Age = Age; } } void SetSex(int Sex) { this->Sex = Sex; } void print() { printf("Age:%d,Sex%d\n", Age, Sex); } };
class Teacher :public Person { private: int Level; public: Teacher() {
} Teacher(int Age,int Sex,int Level):Person(Age,Sex) { this->Level = Level; } void SetLevel(int Level) { this->Level = Level; } void print() { Person::print(); printf("Level:%d\n",Level); } }; void printPerson(Person &p) { p.print(); }
int main() { Person p(1, 2); Teacher t(1, 2, 3);
printPerson(t);
return 0; }
|
在子类中声明一个和父类相同的函数,函数名,变量,返回值都相同,叫函数重写。但是这样子有个问题,就是写法已经固定了,即使传入的是子类的对象,编译器也只会调用父类的print
函数

看反汇编代码就更清晰了

添加关键词
这种时候只要在父类的函数前,添加关键词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 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
|
#include <iostream> #include <stdlib.h> #include<stdio.h> #include<windows.h>
class Person { private: int Age; int Sex; public: Person() {
} Person(int Age, int Sex) { this->Age = Age; this->Sex = Sex; } void Work() { printf("Person:Work()"); } void SetAge(int Age) { if (Age < 0) { this->Age = 0; } else { this->Age = Age; } } void SetSex(int Sex) { this->Sex = Sex; } virtual void print() { printf("Age:%d,Sex%d\n", Age, Sex); } };
class Teacher :public Person { private: int Level; public: Teacher() {
} Teacher(int Age,int Sex,int Level):Person(Age,Sex) { this->Level = Level; } void SetLevel(int Level) { this->Level = Level; } void print() { Person::print(); printf("Level:%d\n",Level); } }; void printPerson(Person &p) { p.print(); }
int main() { Person p(1, 2); Teacher t(1, 2, 3);
printPerson(t);
return 0; }
|
成功输出了Level
,这样子就可以根据传进来的对象来判断使用那个类的函数。


通过反汇编代码看,就是不像刚才那样。调用了父类Person
以后,就进行POP
了。
多态总结
- 多态就是可以让父类指针有多种形态
- C++是通过虚函数实现的多态性
- 虚函数关键词
virtual
虚表
虚函数关键词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
|
#include <iostream> #include <stdlib.h> #include<stdio.h> #include<windows.h>
class A { public: int x; virtual void Test() { printf("A\n"); } }; class B :public A { public: void Test() { printf("B \n"); } };
void Fun(A* p) { p->Test(); }
int main() { A a; B b; Fun(&a);
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
| void Fun(A* p) { 003B1860 push ebp 003B1861 mov ebp,esp 003B1863 sub esp,0C0h 003B1869 push ebx 003B186A push esi 003B186B push edi 003B186C lea edi,[ebp-0C0h] 003B1872 mov ecx,30h 003B1877 mov eax,0CCCCCCCCh 003B187C rep stos dword ptr es:[edi] 003B187E mov ecx,offset _DD962600_cpp@cpp (03BC033h) 003B1883 call @__CheckForDebuggerJustMyCode@4 (03B123Fh) p->Test(); 003B1888 mov eax,dword ptr [p] 003B188B mov edx,dword ptr [eax] 003B188D mov esi,esp 003B188F mov ecx,dword ptr [p] 003B1892 mov eax,dword ptr [edx] 003B1894 call eax 003B1896 cmp esi,esp 003B1898 call __RTC_CheckEsp (03B1249h) }
|
那么当没有虚函数关键词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
|
#include <iostream> #include <stdlib.h> #include<stdio.h> #include<windows.h>
class A { public: int x; void Test() { printf("A\n"); } }; class B :public A { public: void Test() { printf("B \n"); } };
void Fun(A* p) { p->Test(); }
int main() { A a; B b; Fun(&a);
return 0; }
|
看下反汇编代码

1 2 3
| p->Test(); 00CC1A28 mov ecx,dword ptr [p] 00CC1A2B call A::Test (0CC13E3h)
|
这里就直接写死了,只使用父类的A::Test()
:baby_chick:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 没有虚函数关键词 p->Test(); 00CC1A28 mov ecx,dword ptr [p] 00CC1A2B call A::Test (0CC13E3h) 有虚函数关键词 p->Test(); 003B1888 mov eax,dword ptr [p] 003B188B mov edx,dword ptr [eax] 003B188D mov esi,esp 003B188F mov ecx,dword ptr [p] 003B1892 mov eax,dword ptr [edx] 003B1894 call eax 003B1896 cmp esi,esp 003B1898 call __RTC_CheckEsp (03B1249h) 00CC1A2B call A::Test (0CC13E3h) 003B1894 call eax
|
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
|
#include <iostream> #include <stdlib.h> #include<stdio.h> #include<windows.h>
class A { public: int x; virtual void Test() { printf("A\n"); } }; class B :public A { public: void Test() { printf("B \n"); } };
void Fun(A* p) { p->Test(); }
int main() { A a; B b; printf("%d", sizeof(a));
return 0; }
|
当只有一个变量时,A
类大小为8,当去除关键词virtual
时,变为4,但是即使在增多几个virtual
也不会继续增加4,说明,当使用了virtual
时,会自动增加4个字节。

virtual
内存
通过vs2017 -》调试窗口-》内存 中查看virtual
内存地址
可以看到,自动窗口处,当使用了virtual
时,会多一条内存地址

那么如果没有使用virtual
时了。只有x

多出来的那个_vlptr
的地址,跟一下。

记录下来 0x002f140b
是反过来计算的 ,进入反汇编,Ctrl+G 输入,跳到函数入口处,02F18F0h
是个栈,在跳过去

就是Test函数,可以看出,当前对象如果有 虚函数,那么当前对象开始的地方,就不是当前的数据成员了,而是一张表,这张表称为虚表。

有了这个基础,那么看回这里就能理解,为什么编译器可以在使用了virtual
关键词时,可以实现多态了

1 2 3 4 5 6 7 8 9
| p->Test(); 002F1AF8 mov eax,dword ptr [p] 002F1AFB mov edx,dword ptr [eax] 002F1AFD mov esi,esp 002F1AFF mov ecx,dword ptr [p] 002F1B02 mov eax,dword ptr [edx] 002F1B04 call eax 002F1B06 cmp esi,esp 002F1B08 call __RTC_CheckEsp (02F1249h)
|
分析一下,先取参数[p]
,参数是个对象指针,传给eax
,接着把[eax]
的值,就是当前对象的第一个成员取出来赋给edx
,就是虚表的地址,这个虚表的地址就是Test()
的值,然后就把虚表的地址[edx]
传给eax
,然后直接调用eax
。
如果有两个虚函数
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 <iostream> #include <stdlib.h> #include<stdio.h> #include<windows.h>
class A { public: int x; virtual void Test() { printf("A\n"); } virtual void Test1() { printf("A\n"); } }; class B :public A { public: void Test() { printf("B \n"); } };
void Fun(A* p) { p->Test1(); }
int main() { A a; B b; Fun(&a); printf("%d", sizeof(a)); return 0; }
|
结果都是一样,只是当你调用的是第二个虚函数时,edx+4.
1 2 3 4 5 6 7 8 9
| p->Test1(); 01281AF8 mov eax,dword ptr [p] 01281AFB mov edx,dword ptr [eax] 01281AFD mov esi,esp 01281AFF mov ecx,dword ptr [p] 01281B02 mov eax,dword ptr [edx+4] 01281B05 call eax 01281B07 cmp esi,esp 01281B09 call __RTC_CheckEsp (01281249h)
|

虚表总结
- 虚函数 目的是提供一个统一的接口,被继承的子类重载,以多态的形式被调用。
- 如果基类中的函数没有任何实现的意义,那么可以定义成纯虚函数;
- virtual 返回类型 函数名(参数列表) =0;
- 含有春旭函数的类被称为抽象类(abstract class) 不能创建对象
- 虚函数可以被直接使用,也可以被子类(sub class) 重载以后以多态的形式调用,而纯虚函数必须在子类(sub class)中实现该函数才可以使用
- C++中基类采用virtual虚析构函数是为了防止内存泄漏
运算符重载
operator
关键词
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
|
#include <iostream> #include <stdlib.h> #include<stdio.h> #include<windows.h>
class Number { private: int x; int y; public: Number() {
} Number(int x, int y) { this->x = x; this->y = y;
} bool Max(Number& n) { return this->x > n.x &&this->y > n.y; } };
int main() { Number n1(1, 1), n2(2, 2); bool r = n1.Max(n2); 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
|
#include <iostream> #include <stdlib.h> #include<stdio.h> #include<windows.h>
class Number { private: int x; int y; public: Number() {
} Number(int x, int y) { this->x = x; this->y = y;
} bool operator>(Number& n) { return this->x > n.x &&this->y > n.y; } };
int main() { Number n1(1, 1), n2(2, 2); bool r = n1>n2; 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
| class Number { private: int x; int y; public: Number() {
} Number(int x, int y) { this->x = x; this->y = y;
} bool operator>(Number& n) { return this->x > n.x &&this->y > n.y; } bool operator<(Number& n) { return this->x < n.x &&this->y < n.y; } bool operator==(Number& n) { return this->x == n.x &&this->y == n.y; } Number operator++() { this->x++; this->y++; } Number operator--() { this->x--; this->y--; } Number operator+(const Number& p)const { Number num; num.x = this->x + p.x; num.y = this->y + p.y; return num; } Number operator-(const Number& p)const { Number num; num.x = this->x - p.x; num.y = this->y - p.y; return num; } Number operator/(const Number& p)const { Number num; num.x = this->x / p.x; num.y = this->y / p.y; return num; } Number operator*(const Number& p)const { Number num; num.x = this->x * p.x; num.y = this->y * p.y; return num; }
};
|
运算符重载总结
- 成员函数重写运算符时,只有一个参数,结尾有
const
。
模版
在函数中使用模板
函数模板的格式
1 2 3 4
| template <class 形参名,class 形参名,...> 返回类型 函数名(参数列表) { 函数体 }
|
结构体/类中使用模板
类模板的格式为
1 2 3 4
| template<class 形参名,class 形参名,...> class 类名 { ... }
|
看一段代码
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
|
#include <iostream> #include <stdlib.h> #include<stdio.h> #include<windows.h>
void Sort(int* arr, int nLength) { int i; int k; for (i = 0; i < nLength; i++) { for (k = 0; k < nLength - 1; k++) { if (arr[k] > arr[k + 1]) { int temp = arr[k]; arr[k] = arr[k + 1]; arr[k + 1] = temp; } } } }
int main() { int arr[] = { 1,4,3,7,8,9,6 }; Sort(arr, 7); return 0; }
|
这就是一个排序的代码,提出一个,当要排序的形参不是 int
类型时,而是char
类型,或者其他类型时候,就需要从形参开始一个一个改,这样子是挺麻烦的。
这时候使用模板就刚刚好了
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
|
#include <iostream> #include <stdlib.h> #include<stdio.h> #include<windows.h>
template<class T> void Sort(T* arr, int nLength) { int i; int k; for (i = 0; i < nLength; i++) { for (k = 0; k < nLength - 1; k++) { if (arr[k] > arr[k + 1]) { T temp = arr[k]; arr[k] = arr[k + 1]; arr[k + 1] = temp; } } } }
int main() { int arr[] = { 1,4,3,7,8,9,6 }; Sort(arr, 7); return 0; }
|
即使声明不同类型的变量调用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
|
#include <iostream> #include <stdlib.h> #include<stdio.h> #include<windows.h>
template<class T> void Sort(T* arr, int nLength) { int i; int k; for (i = 0; i < nLength; i++) { for (k = 0; k < nLength - 1; k++) { if (arr[k] > arr[k + 1]) { T temp = arr[k]; arr[k] = arr[k + 1]; arr[k + 1] = temp; } } } }
int main() { int arr1[] = { 1,4,3,7,8,9,6 }; char arr2[] = { 1,4,3,7,8,9,6 };
Sort(arr1, 7);
Sort(arr2, 7); return 0; }
|

可以通过反汇编角度看一看,每次调用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 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
|
#include <iostream> #include <stdlib.h> #include<stdio.h> #include<windows.h>
class Base { private: int x; int y; public: Base(int x, int y) { this->x = x; this->y = y; } bool operator>(Base& base) { return this->x > base.x &&this->y > base.y; } };
template<class T> void Sort(T* arr, int nLength) { int i; int k; for (i = 0; i < nLength; i++) { for (k = 0; k < nLength - 1; k++) { if (arr[k] > arr[k + 1]) { T temp = arr[k]; arr[k] = arr[k + 1]; arr[k + 1] = temp; } } } }
int main() { int arr1[] = { 1,4,3,7,8,9,6 }; char arr2[] = { 1,4,3,7,8,9,6 }; Base b1(1, 1), b2(3, 3), b3(2, 2), b4(5, 5), b5(4, 4); Base arr3[] = { b1,b2,b3,b4,b5 }; Sort(arr1, 7);
Sort(arr2, 7);
Sort(arr3, 5); 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 90 91 92 93 94 95 96 97 98 99 100 101 102
|
#include <iostream> #include <stdlib.h> #include<stdio.h> #include<windows.h>
template<class N,class K> struct Number//结构体 { N x; N y;
K a; K b; N Max() { if (x > y) { return x; } else { return y; } } K Min() { if (a < b) { return a; } else { return b; } } };
class Base//类 { private: int x; int y; public: Base(int x, int y) { this->x = x; this->y = y; } bool operator>(Base& base) { return this->x > base.x &&this->y > base.y; } };
template<class T> void Sort(T* arr, int nLength) { int i; int k; for (i = 0; i < nLength; i++) { for (k = 0; k < nLength - 1; k++) { if (arr[k] > arr[k + 1]) { T temp = arr[k]; arr[k] = arr[k + 1]; arr[k + 1] = temp; } } } }
int main() { int arr1[] = { 1,4,3,7,8,9,6 }; char arr2[] = { 1,4,3,7,8,9,6 }; Base b1(1, 1), b2(3, 3), b3(2, 2), b4(5, 5), b5(4, 4); Base arr3[] = { b1,b2,b3,b4,b5 }; Sort(arr1, 7); Sort(arr2, 7); Sort(arr3, 5);
Number<int,char> num; num.x = 1; num.y = 2;
num.a = 3; num.b = 4;
num.Max(); num.Min(); return 0; }
|
纯虚函数
什么是纯虚函数?
- 将成员函数声明为
virtual
- 该函数没有函数体(后跟0)
1 2 3 4 5
| class CBank { public: virtual double GetAnnualRate()=0; }
|
抽象类
- 含有纯虚函数的类,称为抽象类(Abstract Class)
- 抽象类也可以包含普通的函数
- 抽象类不能实例化
1 2
| CBank bank; CBank* pBank = new CBank();
|
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
|
#include <iostream> #include <stdlib.h> #include<stdio.h> #include<windows.h>
class CBank { public: virtual double GetAnnualRate() = 0; };
class CICBCBank :public CBank { private: double m_dPrincipal; public: CICBCBank(double dPrincipal) { m_dPrincipal = dPrincipal; } double GetAnnualRate() { return 0.1; } double GetTotalMoney() { return m_dPrincipal + m_dPrincipal * GetAnnualRate(); } };
class CCCBank :public CBank { private: double m_dPrincipal; public: CCCBank(double dPrincipal) { m_dPrincipal = dPrincipal; } double GetAnnualRate() { return 0.2; } double GetTotalMoney() { return m_dPrincipal + m_dPrincipal * GetAnnualRate(); } };
void showAnnualRate(CBank* pBank[], DWORD nLength) { for (int i = 0; i < nLength; i++) { printf("%.2lf\n",pBank[i]->GetAnnualRate()); } }
int main() { CICBCBank icbc(10000.0); double dMoney = icbc.GetTotalMoney();
CCCBank ccc(10000); dMoney = ccc.GetTotalMoney();
CBank* pBank[] = { &icbc,&ccc }; showAnnualRate(pBank, 2); 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
|
#include <iostream> #include <stdlib.h> #include<stdio.h> #include<windows.h>
class Cobject { private: int x; int y; public: Cobject() {
} Cobject(int x, int y) { this->x = x; this->y = y;
} };
int main() { Cobject obj(1, 2);
Cobject objNew(obj); Cobject*p = new Cobject(obj); return 0; }
|
看下输出,所有值都相同。

从汇编代码看,就是把 obj
的值取出来,然后传过去,达到拷贝效果

浅拷贝
编译器自带拷贝构造函数
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
|
#include <iostream> #include <stdlib.h> #include<stdio.h> #include<windows.h>
class Cobject { private: int m_Length; char* m_strBuffer; public: Cobject() {
} Cobject(const char* str) { m_Length = strlen(str) + 1; m_strBuffer = new char[m_Length]; memset(m_strBuffer, 0, m_Length); strcpy(m_strBuffer, str); } ~Cobject() { delete[] m_strBuffer; } };
int main() { Cobject obj("你好");
Cobject objNew(obj); return 0; }
|
通过char
类型可以看出来,现在的拷贝构造函数,与被拷贝的函数地址是一样的,这种称为浅拷贝。
既拷贝的是指针本身,而不是指针指向的那块内存。
这样子就有一个问题,当 obj
结束时候,调用了析构函数清理了内存时,浅拷贝的地址就会出错。

赋值运算符=
浅拷贝
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
|
#include <iostream> #include <stdlib.h> #include<stdio.h> #include<windows.h>
class CBase { private: int m_Length; char* m_pBuffer; public: CBase(){ m_Length = 0; m_pBuffer = NULL; } CBase(const char* szBuffer) { this->m_Length = strlen(szBuffer)+1; m_pBuffer = new char[m_Length]; strcpy(m_pBuffer, szBuffer); } virtual ~CBase() { delete[] m_pBuffer; } };
int main() { CBase c1("china"),c2("world"); c1 = c2;
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
|
#include <iostream> #include <stdlib.h> #include<stdio.h> #include<windows.h>
class Cobject { private: int m_Length; char* m_strBuffer; public: Cobject() {
} Cobject(const char* str) { m_Length = strlen(str) + 1; m_strBuffer = new char[m_Length]; memset(m_strBuffer, 0, m_Length); strcpy(m_strBuffer, str); } Cobject(const Cobject& obj) { m_Length = obj.m_Length; m_strBuffer = new char[m_Length]; memset(m_strBuffer, 0, m_Length); strcpy(m_strBuffer, obj.m_strBuffer); } ~Cobject() { delete[] m_strBuffer; } };
int main() { Cobject obj("你好");
Cobject objNew(obj); 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
| #include <iostream> #include <stdlib.h> #include<stdio.h> #include<windows.h>
class CBase { private: int m_Length; char* m_pBuffer; public: CBase(){ m_Length = 0; m_pBuffer = NULL; } CBase(const char* szBuffer) { this->m_Length = strlen(szBuffer)+1; m_pBuffer = new char[m_Length]; strcpy(m_pBuffer, szBuffer); } CBase& operator=(const CBase& ref) { m_Length = ref.m_Length; if (m_pBuffer != NULL) delete[] m_pBuffer; m_pBuffer = new char[m_Length]; memcpy(m_pBuffer, ref.m_pBuffer, m_Length); return *this; } virtual ~CBase() { delete[] m_pBuffer; } };
int main() { CBase c1("china"),c2("world"); c1 = c2;
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
| #include <iostream> #include <stdlib.h> #include<stdio.h> #include<windows.h>
class CBase { private: int m_Length; char* m_pBuffer; public: CBase(){ m_Length = 0; m_pBuffer = NULL; } CBase(const char* szBuffer) { this->m_Length = strlen(szBuffer)+1; m_pBuffer = new char[m_Length]; strcpy(m_pBuffer, szBuffer); } CBase& operator=(const CBase& ref) { m_Length = ref.m_Length; if (m_pBuffer != NULL) delete[] m_pBuffer; m_pBuffer = new char[m_Length]; memcpy(m_pBuffer, ref.m_pBuffer, m_Length); return *this; } virtual ~CBase() { delete[] m_pBuffer; } };
class CSub :public CBase { private: int m_nIndex; public: CSub(){} CSub(int nIndex, char* szBuffer) :CBase(szBuffer) { m_nIndex = nIndex; } CSub& operator=(const CSub& ref) { CBase::operator=(ref); m_nIndex = ref.m_nIndex; return *this; } };
int main() { CSub c1("china"),c2("world"); c1 = c2;
return 0; }
|

为什么拷贝构造函数不能显式调用?**
因为拷贝构造函数没有继承,所以不能显式调用。
什么时候需要深拷贝
当你对象成员,是一个指针,指向是一块自己申请的内存,这种情况就必须要用深拷贝。
拷贝构造函数总结
- 如果不需要深拷贝,不要自己添加拷贝构造函数。
- 如果添加了拷贝构造函数,那么编译器将不再提供,所有的事情都需要由新添加的函数自己来处理
- 当自定义拷贝构造函数时,需要自己声明父类,明确告诉编译器,调用父类构造函数。
1 2 3 4 5 6
| MyObject(const MyObject& obj):Base(obj) { this->x = obj.x; this->y = obj.y; }
|
4.如果有重载赋值运算符,必须对所有的属性都要进行处理
5.如果有父类,要显式调用父类的重载赋值运算符。
友元
友元(朋友 元素),友元破坏了面向对象的封装特性,相对于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
| #include <iostream> #include <stdlib.h> #include<stdio.h> #include<windows.h>
class Cobject { private: int x; public: Cobject(){} Cobject(int x) { this->x = x; } }; void PrintObject(Cobject* pObject) { printf("%d \n", pObject->x); }
int main() {
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
| #include <iostream> #include <stdlib.h> #include<stdio.h> #include<windows.h>
class Cobject { friend void PrintObject(Cobject* pObject); private: int x; public: Cobject(){} Cobject(int x) { this->x = x; } }; void PrintObject(Cobject* pObject) { printf("%d \n", pObject->x); }
int main() { Cobject c1(5); PrintObject(&c1); 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
| #include <iostream> #include <stdlib.h> #include<stdio.h> #include<windows.h>
class Cobject { friend class TestFriend; private: int x; public: Cobject(){} Cobject(int x) { this->x = x; } }; class TestFriend { public: void Fn(Cobject* pObject) { printf("%d \n", pObject->x); } };
int main() { Cobject c1(5); TestFriend c2; c2.Fn(&c1); 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
| #include <iostream> #include <stdlib.h> #include<stdio.h> #include<windows.h>
class COutter { private: int m_nOut; public: class CInner//内部类 { private: int m_nIn; public: CInner(){} CInner(int nin) { m_nIn = nin; } }; COutter(){} COutter(int out) { m_nOut = out; } };
int main() { COutter::CInner c; printf("%d\n",sizeof(COutter)); return 0; }
|
大小为4
,而且双方无法访问对方类的成员,彼此之间没有任何关系。

当要实现某种功能,某些类,但是这个类只有当前模块使用,其他人用不到,就可以定义内部类。
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
| #include <iostream> #include <stdlib.h> #include<stdio.h> #include<windows.h>
class COutter { private: int m_nOut; class CInner//私有内部类 { private: int m_nIn; public: CInner() {} CInner(int nin) { m_nIn = nin; } }; public: COutter(){} COutter(int out) { m_nOut = out; } void Fn() { CInner c; } };
int main() { printf("%d\n",sizeof(COutter)); return 0; }
|
内部类总结
实现信息的隐藏
命名空间namespace
解决命名冲突
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
| #include <iostream> #include <stdlib.h> #include<stdio.h> #include<windows.h>
namespace ns1 { int x = 100;
void Fn() { printf("ns1::Fn\n"); } class CObject { public: void Test() { printf("ns1:;Cobject::Test()\n"); } }; }
namespace ns2 { int x = 200;
void Fn() { printf("ns1::Fn\n"); } class CObject { public: void Test() { printf("ns1:;Cobject::Test()\n"); } }; }
int main() { printf("%d\n", ns1::x);
printf("%d\n", ns2::x); return 0; }
|

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
| #include <iostream> #include <stdlib.h> #include<stdio.h> #include<windows.h>
char szBuffer[0x10]; void MyFunction(int nFlag) { static char szBuffer[0x10]; if (nFlag) { strcpy(szBuffer, "Hello"); } else { printf("%s \n", szBuffer); } }
int main() { return 0; }
|
static
初始化
类中的成员不需要初始化,但是加了static
后必须初始化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| #include <iostream> #include <stdlib.h> #include<stdio.h> #include<windows.h>
class CBase { private: int x; int y; static int z; };
int CBase::z = 0;
int main() { return 0; }
|
而且打印CBase
类的大小时候,只有8,说明加了 static
后不是当前这个类的成员。但是加在里面,说明他是一个私有的成员,只允许CBase
这个类使用。

访问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
| #include <iostream> #include <stdlib.h> #include<stdio.h> #include<windows.h>
class CBase { private: int x; int y; static int z; public: void Fn() { z = 10; printf("z:%d\n", z); } };
int CBase::z = 0;
int main() { CBase c; c.Fn(); return 0; }
|

面向对象设计中的static
之静态数据成员
- 静态数据成员存储在全局数据区,且必须初始化,静态数据成员初始化的格式为:
1
| <数据类型><类名>::<静态数据成员名>=<值>
|
- 静态数据成员和普通数据成员一样遵从public,protected,private访问规则:
- 类的静态数据成员有两种访问形式:
1 2
| <类对象名><静态数据成员名> <类类型名><静态数据成员名>
|
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
| #include <iostream> #include <stdlib.h> #include<stdio.h> #include<windows.h>
class CBase { private: int x; int y; static int sum; public: CBase(int x, int y); static int Getsum(); };
int CBase::sum = 0; CBase::CBase(int x, int y) { this->x = x; this->y = y; } int CBase::Getsum() { sum = 10; printf("%d\n", sum); return sum; }
int main() { CBase c(1, 2); CBase::Getsum();
return 0; }
|
定义静态成员函数最大的价值,就是可以直接通过类名直接访问,在没有对象的情况下直接访问

面向对象设计中的static
之静态成员函数
- 出现在类体外的函数定义不能指定关键字
static
- 静态成员之间可以相互访问,包括静态成员函数访问静态数据成员和访问静态成员函数
- 非静态成员函数可以任意地访问静态成员函数和静态数据成员
- 静态成员函数不能访问非静态成员函数和非静态数据成员
- 调用类的静态成员函数的两种方式
1 2 3
| <类名><静态成员函数名>(<参数表>) <对象名><静态成员函数名>(<参数表>) <指针><静态成员函数名>(<参数表>)
|
结语
为什么要写笔记,因为平时看视频时候,开1.5倍速甚至2倍速,看着好像是听懂了,其实写起来还是懵逼的。
但是当自己把听到的知识点,通过文字的方式写出来,才真的会进脑子:keyboard: