avatar

目录
C++笔记

一直都有在有接触C++,但是一直都没有系统的学过,虽然语法上和C很类似,但是学了以后才知道,还是有挺大不同,面向对象真的挺方便的。

为什么学C++了,因为现在用C++写一套dxf的插件时候,用vector写地图数据时候,出现 expression vector subscript out of range的报错,但是一直不知道怎么解决。

封装

结构体的认识

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
#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 的方法,而是通过 内存复制的方式 。

Code
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

为什么结构体会采用这种方式,因为编辑器,默认是不会知道你结构体有多少个参数,每个参数是什么类型,所以要传递参数时,编辑器会在堆栈中先分出一块空间,然后在通过寄存器在局部变量里面一个一个的复制进去。

虽然可以正常达到参数传递的目的,但是如果要传的参数过多时,这样子就会造成大量的内存复制,其实不太好,可以通过指针传递的方式达到相同的目的。

结构体指针传递参数

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
#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;
}

运行,查看反汇编,看下代码。

看下参数传递那里

Code
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++的优势,就是编译器替我们做重复的事情

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 这样子了。

把函数放进结构体里面,这样子编译器就可以替你做这些事情。从汇编代码看

c++
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

这里和刚才的很类似,但是我们没有传参数进去,但是实际上是传,我们没有写,为什么会传了,因为我们把函数写进了结构体里面,编译器就知道了,然后就替我们做了这一步。我们把当前函数的指针传进去,我们只要告诉编译器我们要使用的是哪一个结构体的函数。

这样子也叫类,就是带有函数的结构体

c++
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;
}
};

成员函数

结构体里面的函数

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
#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));
//Student s = { 1,2,3,4 };
//int r = s.Plus();
//printf("%d\n", r);
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
#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));
//Student s = { 1,2,3,4 };
//int r = s.Plus();
//printf("%d\n", r);
return 0;
}

可以看到,结构体大小和成员数量有关,一个int类型占4个字节。

封装总结

  • 什么是封装
    • 将函数定义到结构体内部,就是封装。
  • 什么是类:
    • 带有函数的结构体,称为类。
  • 什么是成员函数:
    • 结构体里面的函数,称为成员函数。

This 指针

通常情况下,编译器都会采用ECX来传递一个结构体的指针,也可以用来识别代码是C写的还是C++写的。

this指针是编译器默认传入的,通常都会使用ECX进行参数的传递,你用或者不用,他都在那。

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

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
#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;
}

Code
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 指针不占用结构体的宽度

构造函数

构造函数,主要用来做初始化操作。

名字和类名一样,没有返回值,想写几个参数就写几个

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
// CPP.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#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。

看下汇编代码。编译器自动调用了构造函数。

有参构造函数

构造函数可以随意定义参数,但是创建对象时,需要把参数带上,不要编译器会报错。

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
#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;
}

无参构造函数

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
#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;
}

构造函数总结

  • 与类同名,没有返回值
  • 创建对象的时候执行,主要用于初始化
  • 可以有多个(最好有一个无参数),称为重载,其他函数可以重载
  • 编译器不要求必须提供

析构函数

析构函数和构造函数很像,多了一个符号 ~,只能是无参的

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
// CPP.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#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函数执行完毕,析构函数就会执行,通过汇编代码来看。

c++
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. 当对象在全局区分配

析构函数总结

  • 只能有一个析构函数,不能重载

  • 不能带任何参数

  • 不能带返回值

  • 主要用于清理工作

    c++
    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);//申请一个 1024 字节的空间
    }
    ~Person()
    {
    printf("析构函数执行\n");
    free(arr);//释放内存
    }
    };
  • 编译器不要求必须提供

继承

继承就是数据的复制

子类可以使用父类定义的变量,这样子就可以减少重复代码的编写。

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
#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;//继承父类age
t.sex = 2;//继承父类age
t.classId = 3;
t.level = 4;
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
#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;
printf("%d", sizeof(t));
//t.level = 4;
return 0;
}

现在看下当前 Teachear的大小,等于 12,现在只有三个变量,一个占4个字节。

如果子类中有父类相同的变量,例如又定义了一个 int age

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
// CPP.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#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));
//t.level = 4;
return 0;
}

16,多了一个重复的变量,但是编译器不管你是否是重复的,他只会将你定义的两个变量加上去,进行重写。

访问子类或父类

因为有重复的变量名,如果要访问父类的变量时就应该告诉编译器

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
#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;
}

多重继承

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 <iostream>
#include <stdlib.h>
#include<stdio.h>
#include<windows.h>

struct Person{//Teacher父类
int age;
int sex;
};

struct Teacher:Person//student 父类
{
int level;
int classId;
};
struct student :Teacher//子类
{
int name;
int number;
};

int main()
{
student s;

return 0;
}

这种时候,s这个对象,不仅能访问 Teacher这个父类,还能访问 Teacher的父类,或者说他爷爷类,Person

多重继承总结

多重继承增加了程序的复杂度,不建议使用

继承总结

  1. 继承就是数据的复制
  2. 减少重复代码的编写
  3. Person 称为父类或者基类
  4. Teacher 称为子类或者派生类

堆中创建对象

创建对象

<1>全局变量区

c++
1
Person p;

<2>栈

c++
1
2
3
4
void Max()
{
Person p;
}

<3>堆:new delete

c++
1
2
3
4
在堆中创建对象:
Person* p = new Person();
释放对象占用的内存:
delte p;

C语言中,要在堆中创建对象, 可以使用malloc()申请一块空间,在C++中,使用new;

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
// CPP.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#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

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
// CPP.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#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);//通过malloc申请一块空间
//Person* p = new Person(1, 2);

//释放对象占用的内存
delete p;
return 0;
}

执行代码看效果,可以很清晰的看到,当用C 通过 malloc 申请一块空间时,不会执行构造函数,只在 使用了 delete以后,会执行析构函数。

但是通常,C语言使用malloc申请空间时候,是使用 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
// CPP.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#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);
//Person* p = new Person[10];
free(p);
//释放对象占用的内存
//delete p;
return 0;
}

当使用free函数清理空间时候,也不会调用析构函数

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
// CPP.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#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);
Person* p = new Person[10];

//释放对象占用的内存
delete p;
return 0;
}

执行代码看效果,可以清晰的看到,这里在堆中创建了10个对象,并且每次创建对象时候都会为对象调用一次构造函数。

因为我们在这里申请了10个对象,所以delete 也要使用[]

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
// CPP.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#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);
Person* p = new Person[10];
//free(p);
//释放对象占用的内存
delete[] p;
return 0;
}

如果申请了多个对象时,使用了delete 而不是 delete[],就会报错。

堆中创建对象总结

  • 使用new 时,用delete 清理
  • 使用new []时,用delete[]清理

引用

&引用类型必须初始化,引用就是起别名

小例子

c++
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。

其他类型

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
// CPP.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#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
ref = 20; //ref 就是x
printf("%d\n", x);

//类
Base b;
b.x = 1;
Base& ref = b;//起个别名叫 ref
ref.x = 2;//ref 就是b
printf("%d\n", b.x);

//指针类型
int* x = (int*)1;
int*& ref = x;//起个别名叫 ref
ref = (int*)2;//ref 就是x
printf("%d\n", x);

//数组
int arr[] = { 1,2,3 };
int(&p)[3] = arr;//起个别名叫 p
p[0] = 4;//p 就是arr
printf("%d\n", arr[0]);
return 0;
}

为什么起了一个别名,并且对他重新赋值以后,也会把原来的值一并修改了?通过汇编代码来看看

c++
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赋值。

把代码改成指针

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 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;
}

可以在看看汇编代码,是一样的。

所以可以说,引用就是指针。使用就是起别名,使用就相当于指针。

指针和引用的区别

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
// CPP.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <iostream>
#include <stdlib.h>
#include<stdio.h>
#include<windows.h>


class Base
{
public:
int x;
};

int main()
{
int x = 1; //x变量初始化

int* p = &x; //指针
int& ref = x;//引用
//运算
p++;
ref++;
//赋值
p = (int*)1;
ref = 100;

return 0;
}

从汇编代码角度来看

声明

可以看,这里没有任何区别,相当于两个指针,在往下看运算。

c++
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的值

c++
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++做运算,引用就是一个别名。

赋值

c++
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

对类进行操作

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
// CPP.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#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等于 bb只是创建的一个对象,对象不能++,也不能赋值,所以编译器会报错。

指针和引用区别总结

  • 指针是一个独立的个体
  • 引用只是一个别名,存储的值永远是存储的对象的地址,对他进行操作就是对他存储的对象进行操作。

引用总结

  • 引用必须赋初始值,且只能指向一个变量,从一而终。
  • 对引用赋值,是对其指向的变量赋值,而并不是修改引用本身的值。
  • 对引用做运算,就是对其指向的变量做运算,而不是对引用本身做运算。
  • 引用类型就是一个“弱化了的指针”。

面向对象程序设计之继承与封装

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
// CPP.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。


#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;
}

这个代码语法上没啥问题,但是设计层面是有瑕疵的。

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
// CPP.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#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类型,所以他是合法的,但是不合理。

所以不能把成员直接暴露出来。就像电脑主机,没可能直接裸露主板出来,通常都会装一个机箱把它包起来。

设计思路

一种好的设计思路,一般不希望给别人访问的,就把它包起来。

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
// CPP.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#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)//判断岁数是否小于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》根本目的是可控

简洁化,编写构造函数

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
// CPP.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#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.SetAge(1);
//t.SetSex(2);
//t.SetLevel(3);
t.Work();

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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
// CPP.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#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.SetAge(1);
//t.SetSex(2);
//t.SetLevel(3);
t.Work();

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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
// CPP.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#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.SetAge(1);
//t.SetSex(2);
//t.SetLevel(3);
t.Work();

return 0;
}

面向对象设计过程封装与继承总结

  • 当不希望被外人访问的成员时,使用私有化private把它保护起来
  • 子类使用构造函数时,父类有有参构造函数时,必须提供无参构造

多态

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
87
88
89
90
// CPP.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#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)//输出Person print函数
{
p.print();
}
void printTeacher(Teacher &t)//通过子类输出父类print函数
{
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++有一个语法,用父类的指针 指向子类的对象。

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
// CPP.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#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

函数重写

要有统一的接口

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
87
88
89
// CPP.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#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,这样子函数就会成为虚函数。

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
87
88
89
// CPP.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#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

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
// CPP.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#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;
}

看下反汇编代码

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
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时候

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
// CPP.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#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;
}

看下反汇编代码

c++
1
2
3
	p->Test();//多态
00CC1A28 mov ecx,dword ptr [p]
00CC1A2B call A::Test (0CC13E3h)

这里就直接写死了,只使用父类的A::Test() :baby_chick:

c++
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 //间接调用
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
// CPP.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#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);
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关键词时,可以实现多态了

c++
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

如果有两个虚函数

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
// CPP.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#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.

c++
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] //第一个虚函数时 [edx] 第二个是 [edx+4] 第三个+8
01281B05 call eax
01281B07 cmp esi,esp
01281B09 call __RTC_CheckEsp (01281249h)

虚表总结

  • 虚函数 目的是提供一个统一的接口,被继承的子类重载,以多态的形式被调用。
  • 如果基类中的函数没有任何实现的意义,那么可以定义成纯虚函数;
    • virtual 返回类型 函数名(参数列表) =0;
  • 含有春旭函数的类被称为抽象类(abstract class) 不能创建对象
  • 虚函数可以被直接使用,也可以被子类(sub class) 重载以后以多态的形式调用,而纯虚函数必须在子类(sub class)中实现该函数才可以使用
  • C++中基类采用virtual虚析构函数是为了防止内存泄漏

运算符重载

operator关键词

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
// CPP.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#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++有一个关键词,可以使运算符重载,就可以让类可以像基本类型一样进行比较

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
// CPP.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#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;
}

运算符重载,就是实现写代码时候方便。其他运算符写法

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
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

模版

在函数中使用模板

函数模板的格式

c++
1
2
3
4
template <class 形参名,class 形参名,...> 返回类型 函数名(参数列表)
{
函数体
}

结构体/类中使用模板

类模板的格式为

c++
1
2
3
4
template<class 形参名,class 形参名,...> class 类名
{
...
}

看一段代码

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
// CPP.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#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类型,或者其他类型时候,就需要从形参开始一个一个改,这样子是挺麻烦的。

这时候使用模板就刚刚好了

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
// CPP.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#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也可以照常使用

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
// CPP.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#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);//0x0D51393h

Sort(arr2, 7);//0x0D5138Eh

return 0;
}

可以通过反汇编角度看一看,每次调用sort都可以看见是不同的地址

定义模板类

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
// CPP.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#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;
}

定义结构体

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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
// CPP.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#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;
}

纯虚函数

什么是纯虚函数?

  1. 将成员函数声明为virtual
  2. 该函数没有函数体(后跟0)
c++
1
2
3
4
5
class CBank
{
public:
virtual double GetAnnualRate()=0;
}

抽象类

  1. 含有纯虚函数的类,称为抽象类(Abstract Class)
  2. 抽象类也可以包含普通的函数
  3. 抽象类不能实例化
c++
1
2
CBank bank; 	//在全局区创建就是全局区 ,在函数内部就是在栈
CBank* pBank = new CBank(); //在堆创建
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
// CPP.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#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;
}

纯虚函数总结

使用抽象类,就是定义一个标准,然后让子类去实现。


拷贝构造函数

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
// CPP.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#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的值取出来,然后传过去,达到拷贝效果

浅拷贝

编译器自带拷贝构造函数

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
// CPP.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#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]; // 堆 malloc
memset(m_strBuffer, 0, m_Length);
strcpy(m_strBuffer, str);
}
~Cobject()
{
delete[] m_strBuffer;
}
};

int main()
{
Cobject obj("你好");

Cobject objNew(obj); //拷贝构造函数

//Cobject*p = new Cobject(obj); //拷贝构造函数
return 0;
}

通过char类型可以看出来,现在的拷贝构造函数,与被拷贝的函数地址是一样的,这种称为浅拷贝。

既拷贝的是指针本身,而不是指针指向的那块内存。

这样子就有一个问题,当 obj结束时候,调用了析构函数清理了内存时,浅拷贝的地址就会出错。

赋值运算符=浅拷贝

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
// CPP.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#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;
}

赋值运算符=和编译器自带的拷贝构造函数,都是将成员的值直接复制,默认都是浅拷贝。

深拷贝

自定义拷贝构造函数

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
// CPP.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#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]; // 堆 malloc
memset(m_strBuffer, 0, m_Length);
strcpy(m_strBuffer, str);
}
Cobject(const Cobject& obj) //这个就是自己定义的拷贝构造函数 当前类引用类型 Cobject&
{
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); //拷贝构造函数

//Cobject*p = new Cobject(obj); //拷贝构造函数
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
#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;
}

赋值运算符=子类

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
#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. 当自定义拷贝构造函数时,需要自己声明父类,明确告诉编译器,调用父类构造函数。
c++
1
2
3
4
5
6
MyObject(const MyObject& obj):Base(obj) //父类拷贝构造函数
{
this->x = obj.x;
this->y = obj.y;
//哪些成员需要拷贝,不能有遗漏
}

4.如果有重载赋值运算符,必须对所有的属性都要进行处理

5.如果有父类,要显式调用父类的重载赋值运算符。


友元

友元(朋友 元素),友元破坏了面向对象的封装特性,相对于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
#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);//这样子默认是不能访问的。因为x 是私有成员。
}

int main()
{

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
#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);//这样子默认是不能访问的。因为x 是私有成员。
}


int main()
{
Cobject c1(5);
PrintObject(&c1);
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
#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);
}
};
//void PrintObject(Cobject* pObject)
//{
// printf("%d \n", pObject->x);//这样子默认是不能访问的。因为x 是私有成员。
//}


int main()
{
Cobject c1(5);
TestFriend c2;
c2.Fn(&c1);
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
#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,而且双方无法访问对方类的成员,彼此之间没有任何关系。

当要实现某种功能,某些类,但是这个类只有当前模块使用,其他人用不到,就可以定义内部类。

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 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

解决命名冲突

c++
1
2
3
4
5
namespace 名称x{
//1.全局变量
//2.函数
//3.类
}
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
#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关键字

私有的全局变量

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
#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后必须初始化。

c++
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成员

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 <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之静态数据成员

  • 静态数据成员存储在全局数据区,且必须初始化,静态数据成员初始化的格式为:
c++
1
<数据类型><类名>::<静态数据成员名>=<值>
  • 静态数据成员和普通数据成员一样遵从public,protected,private访问规则:
  • 类的静态数据成员有两种访问形式:
c++
1
2
<类对象名><静态数据成员名>
<类类型名><静态数据成员名>
  • 同全局变量相比,使用静态数据成员有两个优势
    • 避免命名冲突
    • 可以实现信息隐藏

static静态函数

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
#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()//静态成员函数,不能加static
{
//return x;//不能访问普通数据成员
sum = 10;
printf("%d\n", sum);
return sum; //可以直接访问静态数据成员
}

int main()
{
CBase c(1, 2);
CBase::Getsum();

return 0;
}

定义静态成员函数最大的价值,就是可以直接通过类名直接访问,在没有对象的情况下直接访问

面向对象设计中的static之静态成员函数

  • 出现在类体外的函数定义不能指定关键字static
  • 静态成员之间可以相互访问,包括静态成员函数访问静态数据成员和访问静态成员函数
  • 非静态成员函数可以任意地访问静态成员函数和静态数据成员
  • 静态成员函数不能访问非静态成员函数和非静态数据成员
  • 调用类的静态成员函数的两种方式
c++
1
2
3
<类名><静态成员函数名>(<参数表>)
<对象名><静态成员函数名>(<参数表>)
<指针><静态成员函数名>(<参数表>)

结语

为什么要写笔记,因为平时看视频时候,开1.5倍速甚至2倍速,看着好像是听懂了,其实写起来还是懵逼的。

但是当自己把听到的知识点,通过文字的方式写出来,才真的会进脑子:keyboard:

文章作者: KeyboArd
文章链接: https://www.wrpzkb.cn/VC/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 KeyboArd's Blog
打赏
  • 微信
    微信
  • 支付寶
    支付寶

评论