逆向-C++

C++

C语言和C++的区别

1.C++是对C的扩展,C原有的语法,C++都支持,并且在此基础加了新的语法:封装、继承等

2.为什么扩展:为了方便使用,写代码更高效,但是编译器又处理了更多的工作

3.编译器也在C的基础上帮我们做了更多的工作,写了更多的代码

4.C++也新增很多概念,除了如上所说,还有:类、成员函数、命名空间等

结构体作为参数传递的时候,本质和我们传递整数的时候是没有区别的

小结1
1.结构体可以作为参数传递
2.传递到哪了?堆栈里
3.有什么区别?它不用push传参

把函数定义在结构体内的这种写法,就叫做封装
封装的好处:函数可以非常方便的使用当前结构体内的其他成员

This 指针

1、This 指针是编译器默认传入的,只要把函数定义到结构体内部,编译器都会把This 指针传入进来
2、通常编译器都会用ECX来存储This 指针
3、不管用不用,没参数没返回值,都在那

平时逆向的时候,当看到使用ECX来传递结构体的地址时,肯定就是C++开发的
编译器不允许this进行运算,也不允许对this重新赋值,因为this就是当前结构体的地址,对他运算和赋值没有意义,他就只有一个含义,当前结构体的首地址,只是为了方便使用
小结
1、This 指针是编译器默认传入的,通常使用ecx进行参数的传递
2、成员函数都有This 指针,不管用不用
3、This 指针不能参与运算,不能重新被赋值
4、This 指针不占用当前结构体宽度

构造/析构函数

构造函数

class Counter
{

public:
    // 类Counter的构造函数
    // 特点:以类名作为函数名,无返回类型
    Counter()
    {
        m_value = 0;
    }
         
private:     
    // 数据成员
  int m_value;
}

构造函数的作用:初始化对象的数据成员。

构造函数特点:
函数名称和结构体名称一样
创建对象的时候执行其他函数还要通过对象名)
主要用于初始化,也可以达成我们在创建对象的时候实现什么功能,就可以把代码写到构造函数里
构造函数可以创建多个,想创建几个创建几个,最好有一个无参的,多个构造函数之间的关系称为:重载关系没有
返回值参数
想写几个写几个
编译器不强制要求有构造函数

析构函数

析构函数和构造函数写法相似

	~Study()
	{
		printf("观察是否执行");
	}

和构造函数不同的是,要在前面加一个波浪号
析构函数只允许有一个,不能有多个,不能重载
析构函数里不允许存在参数
主要用于清理工作
编译器不强制提供析构函数

继承

什么是继承?

继承就是复制数据

为什么用继承?

减少重复代码编写

struct Study
{
	int old;
	int six;
};
struct Study1:Study //继承
{
	int banji;
};

这个代码里
Study称为父类,或者基类
Study1称为子类,或者派生类,因为他继承了Study

如果重复不加提示用哪个,编译器默认认为我们使用的是子类自己的成员

多重继承

类成员的访问控制

Public: 使成员对于整个程序内(类内类外)都是可以访问的
Protected: 使派生类也可以访问其数据,但是又不想让其他的函数或类进行访问
Private: 只有他所在类的成员函数可以访问

简单说,private实现的就是一种封装,让类的对象(C++primer里面叫类的用户)不能直接访问,而public让成员可以被程序的任何地方访问到。
派生类的成员或友元只能通过派生类对象来访问基类的受保护对象(即protected对象)。派生类对于一个基类对象中的受保护成员没有任何访问特权。

堆中建对象

创建对象位置

1.全局变量区域

Study a;

2.栈

void main()
{
    Study a;
}

写在函数里的时候,就不在全局变量区分配,就会分配到堆栈中
因为执行函数的时候才会创建,函数执行完毕,分配给对象的空间,就没了

3.堆

C语言有一个函数:
malloc()函数:需要用到内存,不确定大小时,用malloc()申请内存空间,malloc()函数申请的空间就在堆中
C++想把对象创建在堆中,怎么解决?
new函数:Study* a = new Study();
后面的new Study(),这个Study就不是像前面那个一样,是类名了,这里用的是构造函数这个构造函数可以用有参的,也可以用无参的。
如果选择有参数的构造函数,记得要把参数写进去,如:Study(9,9);

#include 
#include 
class Study
{
private:
	int a;
	int b;
public:
	Study()// 构造函数
	{
		printf("Study() 执行 - \n");
	}
	Study(int a, int b)
	{
		printf("Study(Can Shu) 执行 - \n");
		this->a = a;
		this->b = b;
	}
	~Study()//析构函数
	{
		printf("~Study() 执行 - \n");
	}
};

这样创建的对象,不在栈,也不再全局变量区,而是在堆中
在堆中申请内存,使用完了要进行释放,c语言中是用free函数释放内存
在C++中,就用delete释放:delete a;
也就是跟上创建的对象名字

new / delete函数

#include 
#include 
class Study
{
private:
	int a;
	int b;
public:
	Study()
	{
		printf("Study() 执行 - \n");
	}
	Study(int a, int b)
	{
		printf("Study(Can Shu) 执行 - \n");
		this->a = a;
		this->b = b;
	}
	~Study()
	{
		printf("~Study() 执行 - \n");
	}
};
int main()
{
	//堆中创建对象
	Study* a = new Study(2,3)//在堆中创建对象时调用析构函数,除了在堆中申请对象所需的空间外,还调用了析构函数
	//释放对象
	delete a;//delete释放的时候,不仅仅释放了内存,还调用了析构函数
	return 0;
}

new就相当于malloc + 构造函数
数组和类数组的创建方法

数组:

//C 数组创建
int* a = (int*)malloc(sizeof(int)*5);
//释放内存
free(a);
//C++ 数组创建
int* a = new int[5];
//释放内存
delete[] a;
//C 类数组
int* a = (Study*)malloc(sizeof(Study)*5);
//释放内存
free(a);
//C++类数组
Study* a = new Study[5];
//释放内存
delete[] a;

C和C++写法的区别在于:
C的malloc函数只申请空间,不会执行析构函数和构造函数
C++的new,会执行构造和析构函数

引用类型

引用类型就是变量的“别名”

基本类型

//如下:我有一个整数类型a
int a = 5;
//我给a一个别名
int& bac = a;

这里的int&就是一个引用类型
引用类型必须要初始化
我们不可以采用普通变量“双行赋值法”,只能定义的时候直接初始

class YinYong
{
public:
	int a;
};

同样,修改引用类型的,就相当于修改了他本身

数组类型

#include 
#include 

int mian()
{
    int a[] = {7,8,9};
    int(&bac)[3] = a;
    bac[3] = 100;
    
    printf("%d \n", a[3]);
    return 0;
}

指针类型

引用类型,就是变量的一个别名,修改引用类型,就是修改变量本身

引用就是变量的别名,实现的方法就是指针

int a = 1;  //必须初始化
int* b = &a;  //指针类型必须用&来接收变量a的地址
int& bac = a;  //而引用类型不用,这里是差异之一

初始化赋值,没有任何区别(指针与引用)

指针是围绕着自己运算,他能代表自己,但是引用不可以
而且,指针+完宽度以后,存储的地址就变了

指针想怎么改怎么改,指针是独立的,引用看起来是独立的,但是他存储的永远都是代表的那个变量或者对象的真实地址,操作引用的时候,操作的永远都是变量或者对象原来的本身

小结
引用必须赋初始值
对引用修改,就是对指向的变量修改,而不是修改引用本身
对引用做运算,就是对指向的变量做运算,而不是本身做运算
引用就是一个“弱化的指针”

引用作用
函数参数传递

常引用

虚表

参考链接1:https://blog.csdn.net/qq_39642794/article/details/83269047

参考链接2:https://www.cnblogs.com/qianqiannian/p/6037520.html

子类继承父类,子类重写了父类的Test的函数
重写的目的在于:为了保证它们两个有共同的接口,并且子类可以实现父类没有实现的功能
如果父类可以满足需求的情况下,子类是没有必要重写的。
传递父类对象的时候,就会调用父类的函数
传递子类对象的时候,就会调用子类的函数

在C++里,想实现多态性,前提是必须让当前函数是个虚函数,而不是普通函数

#include 
#include 
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(&b);
	return 0;
}

下断、调试、查看反汇编

多态就是通过间接调用实现的

#include 
#include 
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(&b);
	printf("%d \n", sizeof(a));
	return 0;
}

一共有一个成员x,函数不占空间,理应是4字节,但执行后,发现有8个字节
但是如果我们把虚函数标志virtual去掉,就是4字节了。
也就是当我们类里存在虚函数的时候,就会在原来的基础上多4个字节(和虚函数个数无关,一千个虚函数也是多4个字节,自己动手实验)

C++中之所以能实现多态,有两点最重要的
1、间接调用,没有间接调用,代码就是写死的,无法改变
2、有了间接调用,怎么改变,取决于虚表里面的值是什么
所以,多态的本质就是间接调用+虚表

一旦当前对象有虚函数,会在对象最开始的地方,有4字节,这4个字节指向一张表,称为虚表

虚表总结
1、只要类里包含虚函数,不论一个还是一千个,就会生成一张表,这张表的地址就存在当前对象里,存的是表的地址,不是表
2、虚表的位置存在当前对象最开始的位置,正常的话最开始的是数据成员
3、虚表结构就是,有几个虚函数,就往里面写几个虚函数的地址
4、虚表里存储的内容就是函数的地址

运算符重载

#include 
#include 
class Number
{
public:
	Number(int a, int b)
	{
		this->a = a;
		this->b = b;
	}
	bool Biger(Number& c) //改
	{
		return this->a > c.a&& this->b > c.b;
	}
private:
	int a;
	int b;
};
int main()
{
	Number a1(3, 3), a2(2, 2);
	bool e = a1.Biger(a2);  //改
	return 0;
}

bool类型,bool类型占用1字节,只有两个值:true、false,true=1,false=0

operator> //这样就可以告诉编译器,我们要重载大于符号

运算符重载,本质也就是给运算符一个对应的函数

模板

模板可以作用的两个区域:
函数内使用模板
结构体/类中使用模板

模板可以作用的两个区域:
函数内使用模板
结构体/类中使用模板

函数模板

template  返回类型 函数名(参数列表)
//一个模板固定前缀,后面是一个函数声明
{
	函数体
}

模板的本质就是编译器帮我们生成x份不同的函数,根据传递的东西,找到不同的函数地址

纯虚函数

虚函数
1、函数前面加上virtual,这个函数就会变成虚函数
2、没有函数体,后面跟0

class CBK {
public:
	virtual double GetRv() = 0;
};

上面这个函数,没有函数体,直接跟=0结束,这样的函数称为纯虚函数

包含纯虚函数的类,都称为抽象类(Abstract Class)
抽象类里面也可以写普通函数
抽象类不能实体化

class CBK {
public:
	virtual double GetRv() = 0;
};

上面这个纯虚函数,就没法使用如以下的语法:

CBK cbk;  //全局或者栈创建对象
CBK* bcbk = new CBK();   //堆里创建对象

如果使用实体化语法,就会报错

抽象类看成是一个标准,任何该类的子类都必须尊重的标准

有时候当我们子类很多的时候,纯虚这个标准可以提供给我们方便

对象拷贝

编程时存储对象可以用:数组、链表等

拷贝的本质其实就是内存复制

重载赋值运算符
对象拷贝的两种形式:
1、使用拷贝构造函数
2、使用“=”运算符

内部类

友元:https://www.runoob.com/cplusplus/cpp-friend-functions.html

友元friend 函数,把这个函数做一个声明,私有成员就可以被友元声明过的使用

在别的类里面定义的类,叫做内部类

#include 
#include 
class FObject
{
public:
	FObject()
	{
	}
	FObject(int a, int b)
	{
		this->a = a;
		this->b = b;
	}
	class FObject2  //内部类
	{
	public:
		FObject2()
		{
		}
		FObject2(int c, int d, int e)
		{
			this->c = c;
		}
	private:
		int c;
	};
private:
	int a;
	int b;
};
int main()
{
	printf("%d \n", sizeof(FObject));
	return 0;
}

内部类FObject2和外部类FObject,有什么关系?
唯一的关系就是:内部类受到外部类的public或者private的影响

命名空间

解决命名冲突的问题
原来的时候,我们知道两个函数命名不能一样
比如有一个函数名称叫做Test()
另一个函数的名字就不能叫Test()了
但是后来,我们学了类(Class)
这样就可以把不同函数命名于不同的类里,这样他们属于不同的类,就不会有命名冲突问题
但是大项目开发时候,避免不了的就是命名冲突问题

命名空间使用方法:

namespace 名称x
{
    //全局变量
    //类
    //函数
}
namespace 名称y
{
    //全局变量
    //类
    //函数
}

比如:

#include 
#include 
namespace Test1
{
	int a = 100;
	void Prf()
	{
		printf("Good! \n");
	}
}
namespace Test2
{
	int a = 100;
	void Prf()
	{
		printf("Good 2 \n");
	}
}
int main()
{
	return 0;
}

不可能像原来那样直接输入a,因为是无法解析无法识别的,需要加上作用域或者作用范围符号::才能识别

int main()
{
	printf("%d \n", Test2::a);
	Test2::Prf();
	return 0;
}

可以通过using namespace,可以省略作用范围的::的添加

void Fn()
{
	using namespace Test1;
	Prf();
}
int main()
{
	using namespace Test2;
	printf("%d \n", a);
	Prf();
	Fn();
	return 0;
}
namespace Test2
{
	int a = 100;
	void Prf()
	{
		printf("Good 2 \n");
	}
}
void Prf()
{
	Test1::Prf(); //冲突的时候,虽然写了using namespace,但是编译器仍然无法识别,需要手动在写一次
}
int main()
{
	using namespace Test2;
	printf("%d \n", a);
	::Prf(); //普通函数在全局空间,不属于任何命名空间等,所以直接加上两个::代表使用全局的普通函数
	return 0;
}

static关键字

简明释义:用static修饰的就是一个全局变量,只不过是一个私有的全局变量

全局变量的特点
分配在全局区,程序启动的时候这块内存就已经存在了
一个地方对全局变量做了修改,其他地方都有体现

static和全局变量唯一的区别就是:
1、全局变量在哪都可以访问,可以被所有东西使用
2、而经过static修饰的,只可以在当前函数 或者 当前文件下使用

static:https://www.runoob.com/w3cnote/cpp-static-usage.html

你可能感兴趣的:(c++,安全)