前言
进阶学习面向对象C++语言的底层知识;学习以下内容需要有一定的编程基础;后续会不断更新,排版会随着内容的调整而调整;
一、结构体
1. 定义
是C++的一个数据类型,与int、char、float等同一等级;
2. 结构体表现形式
1)结构体中可以存储变量、函数等;如果函数内容比较多,则会使得结构体看起来非常庞大,因此函数一般在结构体里声明,实现是写在结构体外面; 2)结构体可以声明变量、函数以及继承其他结构体; 3)在存储、创建、调用、继承过程中,结构体除了在权限上有区别,其余地方和类都是一样的。
3. 结构体作用
结构体存储的数据类型可以是丰富多样的,这就弥补了存储多个数据但得同类型的数组的不足;
4. 结构体特点
1)结构体创建后是保存在堆中的; 2)结构体作为参数传入函数时,首先会将结构体所有内容复制一遍一个个压入栈中;若作为返回值时,会从栈中复制一遍取出来;结构体定义不要定义为局部变量; 3)由于2的特点传结构体或返回结构体时使用指针的形式,那么函数内部使用的结构体内容还是堆中的,只是通过指针指向的结构体首地址去堆中取。这样传将有利于资源合理利用。
5.结构体字节对齐
聚焦问题:字节对齐是一个空间和时间的问题,一字节对齐不会有空间浪费,但在搜索时间上会比较慢;若选择合适的对齐空间,可以节省搜索时间; 本机宽度:本机有32位、64位等等的差别,不同宽度对齐最优对齐参数不同; 对齐参数:结构体对齐数是选择成员宽度和设置对其参数值小的进行对齐,确定对齐数后结构体内存必须是对齐数的整数倍。对齐参数可以取值为1、2、4、8,默认为8,代码如下:
#pragma pack(n)
#pragma pack()
对齐原则: –d
问题:结构体存储同样的数据类型和元素个数;只是写的向后顺序不一样,导致结构体的占用空间不同;
6. 结构体和类的区别:
结构体和类的区别主要是权限。权限体现在继承和声明成员;结构体声明或继承的所有成员默认都是public,类声明或继承的成员默认都是private。
二、指针
- 内存分布:
代码区:代码; 栈:参数、局部变量; 数据区:主要有全局变量区和常量区,其中全局变量存储全局变量,是可读,可写的;常量区存储的是常量,是可读不可写。
char* x = "china"
char[] y = "China"
- 代码解释
所有的代码通过编译器后转成了汇编。每行代码都有对应的硬编码,硬编码是存储在一块地址中的,每个硬编码通过反汇编引擎翻译成汇编语句。
1.指针函数
定义:返回值是指针的函数;
2.函数指针
1)定义 每个函数都会存放在一块地址中,一个指针变量去指向这块地址的首地址,而这个指针变量就称为函数指针; 2)声明
int (*pFun)(int,int);
3)特性 -pFun的宽度:4 -赋值:
pFun = (int (*)(int,int)10;
-运算: 函数指针不能做++,–,相减。因为函数体内部代码不同,其*pFun宽度不同,但可以做比较; 4)作用 ----加载动态链接库(DLL);首先通过逆向分析出来了DLL库中的函数地址、返回值和参数,然后就可以通过指针函数去调用该函数; ----将硬编码复制到数据区,然后定义已给函数指针,给指针赋值硬编码的首地址,再通过函数指针调用即可; ----在对函数代码进行加密,自己保存一串密钥,使其对硬编码进行数据运算,解密时使用密钥进行反运算即可;
3.数组指针
例子1:char* arr[5]; 解释:这里arr是一个数组,数组中的每一个数据都是char*类型,一个char*类型占用4个字节的空间用于存储地址,那么这个数组总共占用20个字节;
例子2:定义是:char (*px)[5];赋值时:px = (int (*)[5])10; 解释:变量为px,px存储的是一个5个char类型的数组首地址,sizeof(px)则为4;px+=3后px的十进制为1x5x3=15; 这里的px和px区别:*px是一个int数组类型,px是一个int (*)[5]类型;*px和px的地址是一样的,他们都是指针,但是他们的宽度不同,px的宽度是5,*px的宽度是1; 作用:这种写法就可以将一维数组用二维数组的方式操作;
例子3:定义是:char (*px)[2][4];求*(*(*(p+2)+3)+4) 解释:变量为px,宽度为2x4x1 = 8,*p的宽度是4x1 = 4,**p的宽度则为1,因此*(*(*(p+2)+3)+4)访问的值是从首地址往后偏移8x2+4x3+1x4=32个字节;
总结:例子1表示是数组中存储的指针,例子2和例子3是指针数组(用指针操作多维数组)。
4.结构体指针
1)例子:假设student是一个3个int类的结构体;student* A=(student*) 100,进行A++运算后,十进制打印A是112,若student** A=(student**) 100,进行A++运算后,十进制打印A是104; 解释:student结构体存储的宽度为12,在A进行运算时去掉一个星则类型变为student,此时100+12=112;同样student**变为student*,此时student*宽度是4,所以A++后,A的十进制为104;
2)结构体指针存储的不一定只能存结构体,例如:student是一个包含int、char、short类型的结构体;此时 int data = 100; student* A = (student*)data; print(“%d,%d,%d”%(A->one,A->two,A->three)) 输出第一个值是100,但第二第三个值是一个乱码; 解释:A存储的是将data作为首地址,取student宽度的地址范围作为*A的可访问的地址;此时只有第一个4字节地址是正确的后面的所有地址存储的都是乱序的。
3)底层调用约定主要有cdCall,stdCall,msCall三种: cdCall:该调用约定主要有外平栈,参数从右往左传这两个特点;
5.多重指针
1)问题:char* A、char** B、char*** C他们之间有什么区别呢?该怎么使用? 特点一:在指针运算时,类型需要去星,A++后时在A的初始地址上加上char类型的宽度(1字节);同理B++是在B的首地址上加char*类型的宽度(4字节),char**类型宽度也是4字节; 特点二:“*”取值时,类型需去星,如 *A的类型为char,*B的类型为“char*”类型;
三、类
(一).this指针
1. 定义:
this是一个指针,该指针只做一件事就是指向对象首地址。
2. 表现形式:
1)创建类对象是编译器会默认生成一个this指针,在类内部可以使用this指针去获成员变量和函数; 2)若类中有成员函数,编译器会将this指针作为成员函数的第一个参数传入;
3. 作用:
1)区分那些是参数,哪些是成员; 2)可以返回当前对象的首地址; 3)编译器不允许使用者对this指针赋值;
(二)类的控制权限
1.定义:
类等控制权限有三个关键字,分别是private(本类成员可以使用)、protect(子类成员可使用)、public(其他任何地方都可以使用);注意在类外面声明并定义的一个函数,该函数里面创建了类对象,此时不能通过对象去访问private或protect中的内容。
2.作用:
1)方便编写者管理。一般public中的内容不能随意改动,若有不确定的内容可以写到private中,后续更改时也不用改其他类使用该成员的代码。
四、继承
1.定义:
2.作用:
3.机制:
1)继承本质就是复制。子类继承父类,实际上将父类所有的成员复制到子类; 2)继承时会有一个控制权限,若未选择继承权限,那么继承的所有成员都是private成员,尽管父类中有声明是protect或public;
五、多态
(一)虚函数
1. 定义:
虚函数是出现在类中的,是一个用于面向对象的多态的,有虚函数和纯虚函数两种;
2. 虚函数的作用:
3. 虚函数底层:
- 虚函数的位置:
首先一个类创建时,都会有已给this指针,这个指针指向地址所存的东西即是该类下所有内容,若类中有虚函数,那么该内容的首地址所存储的内容将是一个虚函数表,虚函数表中存的东西就是虚函数的地址。 - 虚函数的特点:
1)不管一个类中有多少个虚函数,this指针下存的都只有一个虚函数表的地址; 2)使用对象"."这种形式调用的话,虚函数和普通函数在底层的表示都一样(都是直接调用形式);若使用对象指针的形式去调用,则虚函数是间接调用,普通函数是直接调用; 3)调用时可以通过指针去取出要调用的虚函数地址(该地址是一个4字节,32位系统一般用int类型表达),然后创建一个函数指针,将虚函数地址的int类型强转为函数指针类型,再实行调用; - 虚函数表存储的虚函数内容:
1)多继承无函数覆盖:若继承俩个父类,那就有两个虚函数表,第一个虚函数表只有第一个继承的虚函数地址和子类虚函数地址(每个虚函数表地址都是4字节); 2)多继承有函数覆盖:覆盖的哪个虚函数,虚函数就在被覆盖的那个虚函数表里; 3)多重继承无函数覆盖:只有一个虚函数表,子类虚函数表根据继承的顺序依次从基类到子类的顺序存储 4)多重继承有函数覆盖:和多继承有函数覆盖同理; 5)
(二)多态
1.定义
绑定:调用的代码与函数真正的地址关联的这个过程; 编译期绑定:在编译时就以及确定调用内容的真正地址。普通成员属性和函数都是编译时绑定; 动态绑定(运行期绑定):在运行时才能确定调用的内容的真正地址,虚函数会在运行时绑定(因为编译时绑定的是虚函数表地址,虚函数真正的地址在虚函数表中); 多态:动态绑定的一种形式多态,指的是类中成员函数可以有多种形态、行为(通过虚函数进行变换);
2.作用
1)若子类继承父类时,由于继承的特性,父类的属性会在子类地址的首位,指针指向的首地址就是父类的地址; 2)问题:若无虚函数就算子类重写父类的函数,通过子类去调用重写的函数,此时调用的只是父类被重写的函数; 3)解决方案:将重写的函数声明为虚函数;此时用子类指针调用则指向子类重写的函数,用父类指针调用指向的就是父类被重写的函数。 4)应用:多态用于析构函数,当用父类指针访问子类对象时,调用完子类对象后此时使用的析构函数将是子类的析构函数;
总结
|