C++类-this指针
基础概念
C语言中,结构体作为函数参数时传参传的是副本,而数组传递的是指针,所以我们将结构体作为参数时,最好传入指针
封装-就是将函数放入结构体内部,每次调用函数都会传递当前结构体的首地址,这样我们使用里面的变量就会比较方便
类-这个结构体就叫做类,在结构体插入函数时,结构体大小不变,因为函数不在结构体内部,可以看到sizeoftest=8,也就是两个int
对象-当使用使用类型创建变量时,创建的名称就叫做对象,比如Base base,Base是类,那么base就叫做对象
成员-里面的参数都是成员
成员函数-顾名思义,结构体中函数类型的成员
this指针
在反汇编中,调用类中的函数之前会先把结构体对象的首地址传入ecx中,ecx存放的值就是this指针,我们可以通过它来使用结构体成员,this->x,this->y(在类的函数中),当函数参数和结构体变量同名时,使用this可以帮助编译器区分
我们知道指针可以进行加法减法操作,但是this指针不能进行这些操作,编译器不允许
继承、构造-析构函数
构造函数
首先他是个函数,其次他没有返回值,第三他跟当前的类名是完全一样的
他跟成员函数不同,他是在创建对象的时候使用的
作用:通过构造函数,在创建对象的同时给成员赋值
注意事项:构造函数不是一定要有的,但是定义了构造函数就一定要使用,否则会报错
重载构造函数:多个构造函数,但是构造函数的参数个数不能一样
只要函数的参数个数或者参数类型不一样,就可以存在多个同名函数,这些函数就是重载函数-成员函数
析构函数
不是必须提供的-当使用分配器需要用到
在类中,当我们malloc时,在堆中开辟空间,但是堆中的空间使用完一般要释放,而我们无法知道什么时候才可以释放掉这个堆的空间-因为其他函数可能会继续使用。
但是,当对象不再使用的时候(对象为局部变量时),堆就可以释放掉了
析构函数-无需调用、不能重载、名字与类名相同并在其之前加个~
1 2 3 4 5 6 7
| ~Persion()
{
free(arr);
}
|
继承
本质是数据的复制
当在多个结构体存在相同的成员时,我们可以将相同的成员提取出来创建一个新的结构体,在使用时只需要
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| struct persion { int age; int sex; }
struct student:persion { int grade; int count; }
struct teacher:persion { int level; int p; } teacher s; student t;
|
上面的代码在teacher和student结构体都有四个成员,相同成员有两个
1、Persion称为父类或者基类
2、teacher、student称为子类或者派生类
3、s、t称为对象或者实例
4、可以用父类的指针指向子类的对象
示例:
所以我们取得子类对象地址,就可以通过其来访问父类成员,但是不能访问子类成员,因为是Persion类型的指针
但是不能用子类指针指向父类对象
会一直往上继承父类
当多重继承出现相同的名称是,需要告诉编译器是谁的a,z.x::a,z.y::a
可以同时继承两个父类
权限控制
在类中,如果我们将函数写入会让类变得十分庞大,所以可以在类中进行函数声明,然后再去类外实现,但是我们需要告诉编译器,该函数属于谁,就需要用到 void 类名::函数名(函数参数)
public和private
存储的地方没变,只是告诉编译器不能直接访问private
可以修饰函数也可以修饰变量
public是指该成员在哪里都可以使用,不用担心被修改,所以一旦发布成public成员,是不能修改名字的。而private这个成员只用于内部使用,不要在其他地方使用
一般不想被外部访问的或者以后还会修改的就发布为private
常见的类的定义应该是把变量设为private,将修改private的方法-也就是函数发布为public,并在其中加入一定的限制
使用指针可访问private成员
class
class和struct的不同是权限不一样,class默认为private,struct则为public,注意class的构造函数也需要声明为public
继承
如果按照struct的格式进行继承,编译器会修改成员属性为private,所以如果需要访问,需要在继承时使用:public 父类名,可以看到我们只能继承public类的,一旦修改为private就会报错
private成员只能在当前类被使用,不能被继承
编译器会默认生成构造函数,如果存在继承关系,他会先调用父类的构造函数,因为父辈的内容也需要初始化
我们可以先得到子类的地址,然后访问,即使父类的成员是private,也会被复制过来,只是不能直接访问,下面可以看到这是3个int的大小
但是我们可以知道子类的地址,然后通过指针访问私有的父类
重载
函数重载
运算符重载
虚函数
关键字:virtual
当直接访问类中的函数时,都是直接call,而当使用指针访问时,虚函数是间接call,也就是取地址后再call
当添加一个虚函数时,类的大小增加了四字节
当类中存在两个虚函数时依然增加四字节
这是因为在对象中存在虚函数时会创建虚函数表用来存储各个虚函数的地址,并且虚函数表存储的位置是对象的首地址
eax的值也就是this指针,第一个是虚函数表的地址,后两个是类的成员。
所以这两处调用虚函数时先将eax地址(this指针指向的地址)存储的值放入edx中,然后再取出edx地址中的值进行call,使用指针进行虚函数的调用就是虚函数的虚调用,是虚函数加载完成之后调用的
虚调用和实调用
当类中有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
| #include<string> #include <iostream> using namespace std; class Base { public: int x; int y; Base(); virtual void Function_1() { cout << "Function_1!" << endl; } virtual void Function_2(); }; Base::Base() { x = 0; y = 1; } void Base::Function_2() { cout << "Function_2!" << endl; return ; } int main() { Base test; typedef void(*pfunc)();
pfunc fp; for (int i = 0; i < 2; ++i) { fp= (pfunc) * ((int*)(*(int*)&test) + i); fp(); } return 0; }
|
Hook虚函数
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
| #define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<Windows.h> #include<string.h>
class Test_virtual { public: virtual void Print(); virtual void Print2(); }; void Test_virtual::Print() { printf("This is a Virtual Function\n"); }
void Test_virtual::Print2() { printf("This is a Virtual Function2\n"); } void TrueFunc() { printf("This is the True Function\n"); }
typedef void (*func)(char*); int main(int argc, char* argv[]) { Test_virtual mytest; Test_virtual* test = &mytest; int vfunc_addr = *(int*)(&mytest);
test->Print2(); DWORD lpflOldProtect = 0; VirtualProtect((void*)vfunc_addr, 0x1000, PAGE_EXECUTE_READWRITE, & lpflOldProtect); *(((int*)vfunc_addr)+1) = (int)(TrueFunc); test->Print2(); return 0; }
|
使用虚调用对虚函数进行调用,并且在这之前先对虚函数表中存储的虚函数地址修改为我们想执行的函数地址
单继承覆盖函数
子类中对父类的虚函数进行重写
多层继承无覆盖
可以看到此时继承了两个父类,base类的大小为8,所以存在两个虚表
对两个虚表进行分别调用打印,发现子类的虚函数只在第一个父类的虚表中,第二个虚表只存放第二个父类的虚函数
多层继承会覆盖掉父类的虚函数
多重继承继承和覆盖
对于子类中的虚函数对父类的虚函数进行重写覆盖(即函数名、参数类型和个数相同)时,调用的时候会调用覆盖后的
多态
多态即一个对象表现出多种行为
绑定就是将函数的调用与地址关联起来
前期绑定
也叫做编译时绑定
晚绑定、动态绑定-运行时绑定
动态绑定即多态,多态使得我我们可以通过父类指针来访问子类,而晚绑定和动态绑定的对象都是虚函数
例子
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
| #include<string> #include <iostream> using namespace std;
class base { public: int x; public: base() { x = 30; } void Func_1() { cout << "base:Func_1!" << endl; } virtual void Func_2() { cout << "virtual-base:Function2!" << endl; } };
class Base :public base { public: int x; public: Base() { x = 20; } void Func_1() { cout << "Base:Func_1!" << endl; } virtual void Func_2() { cout << "virtual-Base:Function2!" << endl; } };
int main() { Base test; base* pb = &test; cout << pb->x << endl; pb->Func_1(); pb->Func_2(); return 0; }
|
运行结果
我们通过将子类对象地址赋值给父类指针,然后使用父类指针访问可以发现:在父类和子类都定义了相同的变量x,函数Func_1(),虚函数Func_2(),但是在打印时x和Func_1都是父类的结果,而Func_2则是子类的结果,这是因为前两者为静态绑定-即编译时就写好了所以位直接call地址,而后者为虚函数,为动态绑定,子类中对父类的虚函数进行重写
使用
所以析构函数需要定义为虚函数,即virtua ~Base(),这是因为当我们使用父类指针访问子类对象时,访问完成后会调用父类的析构函数(若不使用虚函数)
模板
STL基础知识
stl的容器用法
https://www.jianshu.com/p/497843e403b4
stl容器详解
https://www.cnblogs.com/sea520/p/12711554.html
operaotr
https://zhuanlan.zhihu.com/p/353189480
测试
String
string使用动态内存分配来存储字符串
string初始化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| #include <iostream> using namespace std; int main() { string s1 = "aaaa";
string s2("bbbbb");
string s3 = s2;
string s4(10, 'd'); return 0; }
|
basic_string是一个类模板
string遍历
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| string str("abcdefg");
for (int i = 0; i < str.length(); ++i) { cout << str[i] << endl; }
for (int i = 0; i < str.length(); ++i) { cout << str.at(i) << endl; }
for (string::iterator it = str.begin(); it != str.end(); it++) { cout << *it << endl; }
|
string打印
1 2
| string str("abcdefg"); cout<<str<<endl;
|
这里的是char,而前面打印单个字符是char_traits(字符特征类)
std::operator是运算符重载
string与char*的转换
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| string str("aaaaa"); const char* p = str.c_str();
const char* p1 = "abcdedg"; string str2 = p1;
char buf[128] = { 0 }; str.copy(buf, 3, 0);
|
string拼接
1 2 3 4 5 6 7 8 9 10 11 12
| string s1 = "123456"; string s2 = "67890";
string s3 = s1 + s2;
string s4 = s1.append(s2);
cout << s3 << endl; cout << s4 << endl;
|
string查找和替换
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
| string s1 = "hello hello hello hello 1234 7686";
size_t index1 = s1.find("hello", 0); cout << index1 << endl;
size_t index2 = s1.find_first_of("hello"); cout << index2 << endl;
size_t index3 = s1.find_last_of("hello"); cout << index3 << endl; int count = 0; size_t offindex = s1.find("hello", 0); while (offindex != string::npos) { cout << "索引:" << offindex << endl; count++; offindex++; offindex = s1.find("hello", offindex); }
size_t offindex1 = s1.find("hello", 0); while (offindex1 != string::npos) { s1.replace(offindex1, strlen("hello"), "welcome"); offindex1 += strlen("welcome"); offindex1 = s1.find("hello", offindex1); } cout << "替换后的字符串:" << s1 << endl;
|
string区间删除和插入
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
| string s1 = "hello1 world!";
size_t index = s1.find('1', 0); if (index == string::npos) { cout << "not found!" << endl; } else { s1.erase(index, 1); cout << s1 << endl; }
s1.erase(s1.begin(), s1.begin() + 3); cout << s1 << endl;
s1.insert(0, "AAA"); cout << s1 << endl;
|
operator运算符重载
string全部大小写转换
需要先include algorithm
1 2 3 4 5 6 7 8 9 10
| string s1 = "abcdefg"; string s2 = "ABCDEFGHIJKL";
transform(s1.begin(), s1.end(), s1.begin(), ::toupper); cout << s1 << endl; transform(s2.begin(), s2.end(), s2.begin(), ::tolower); cout << s2 << endl;
|
Vector
vector是将元素放到动态数组中加以管理的容器。vector容器可以随机存取元素,可以使用[]运算和at方式存取
vector在尾部添加或者移除元素非常快,在中间操作时非常耗时,因为需要移动元素
vector基本用法和遍历
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<vector> using namespace std; int main() { vector<int> v1;
v1.push_back(1); v1.push_back(2); v1.push_back(3);
for (vector<int>::iterator it = v1.begin(); it != v1.end(); it++) { cout << *it << ' '; }
v1.front() = 44; cout << endl << "头部元素为:" << v1.front() << endl; for (int i = 0; i < v1.size(); ++i) { cout << v1[i] << ' '; } v1.back() = 99;
cout << endl << "尾部元素为:" << v1.back() << endl;
v1.pop_back();
for (int i = 0; i < v1.size(); ++i) { cout << v1.at(i) << ' '; }
return 0; }
|
vector初始化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| vector<int>v1; v1.push_back(1); v1.push_back(2); v1.push_back(3); v1.push_back(4);
vector<int>v2 = v1;
vector<int>v3(v1.begin(), v1.begin() + 1); vector<int>v4(v1.begin(), v1.end()); vector<int>v5(3, 9);
|
vector的push_back强化
push_back是在当前vector的内存末尾拷贝元素进入容器。注意这个地方可能产生浅拷贝,所以容器中的对象要支持拷贝操作。另外,如果vector初始化了个数,而不初始化具体的值,push_back也只会在最后面追加
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| vector<int> v(10);
cout << v.size() << endl;
v.push_back(100);
cout << v.size() << endl;
for (vector<int>::iterator it = v.begin(); it != v.end(); ++it) { cout << *it << ' '; } return 0;
|
1 2 3
| 10 11 0 0 0 0 0 0 0 0 0 0 100
|
vector的元素删除和插入
vector的删除,是根据位置进行删除,如果想删除某个元素,需要找到当前元素的迭代器位置,再进行删除
删除之后后面的元素依次前移,相当于迭代器自动指向下一位置
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
| vector<int>v(10); for (int i = 0; i < v.size(); ++i) { v[i] = i; }
v.erase(v.begin(), v.begin() + 3); for (vector<int>::iterator it = v.begin(); it != v.end(); ++it) { cout << *it << ' '; } cout << ' ' << endl; v.erase(v.begin() + 3); for (int i = 0; i < v.size(); ++i) { cout << v.at(i) << ' '; } cout << ' ' << endl;
v.push_back(2); v.push_back(2); for (int i = 0; i < v.size(); ++i) { cout << v[i] << ' '; } cout << ' ' << endl; vector<int>::iterator it = v.begin(); while (it != v.end()) { if (*it == 2) { it = v.erase(it); } else { it++; } }
for (int i = 0; i < v.size(); ++i) { cout << v[i] << ' '; } cout << endl; v.insert(v.begin() + 3, 11); for (int i = 0; i < v.size(); ++i) { cout << v[i] << ' '; } cout << endl; v.insert(v.begin(), 3, 11); for (int i = 0; i < v.size(); ++i) { cout << v[i] << ' '; }
|
deque
deque是一个双端数组容器,可以在头部和尾部操作元素