C++阶段总结第三部分——八千字面向对象总结。三期C+

73 阅读11分钟

    Test1(const Test1 &p)
{
a = p.a;
//b = p.b;   编译器浅拷贝的做法,次数运用到指针是错误的
b = new int(*p.b);
}
private:
int a;
int *b;
};

1.2.6初始化列表

作用:C++提供了初始化列表语法,用于初始化列表

语法:构造函数():属性1(值1),属性2(值2).....{}

class Test1{
public:
Test1(int aa, int bb) :a(aa), b(&bb)
{

    }
private:
int a;
int *b;
};

1.2.7类对象作为类成员

C++类中的成员可以是另一个类的对象,我们称该成员为 对象成员

class A{};
class B
{
A a;
};

当创建B对象时,先构造类对象,再构造本身;析构的时候,先析构自己,再析构类对象

1.2.8静态成员

静态成员就是在成员变量和成员函数之前加上ststic关键字,称为静态成员

静态成员分为:

1,静态成员变量

  • 所有对象共享一份数据
  • 在编译阶段分配内存
  • 类内声明,类外初始化

    static void show()
{
a = 20;
cout << "访问静态成员函数" << endl;
}

2,静态成员函数

  • 所有对象共享同一个函数
  • 静态成员函数只能访问静态成员变量,因为非静态成员变量不能区分出是哪一个对象的成员变量

static int a;

访问方式:

  1. 通过对象访问
  2. 通过类名访问
  3. 类外访问不到静态私有函数

    A b;
b.show();   //通过对象

    A::show();     //通过类名

#include<iostream>
using namespace std;

class A
{
public:
	static void show()
	{
		a = 20;
		cout << "访问静态成员函数" << endl;
	}

private:
	static void showP()
	{
		a = 20;
		cout << "私有静态成员函数" << endl;
	}
	int b;
	static int a;
};
int A::a = 10;



int main()
{
	A b;
	b.show();   //通过对象

	A::show();     //通过类名

	//A::showP();    //错误,因为不能访问私有静态成员函数

	system("pause");
	return 0;
}

1.3C++对象模型和this指针

1.3.1成员变量和成员函数分开存储

在C++中,类的成员变量和成员函数分开存储

只有非静态成员变量才属于类的对象上

  1. 空对象占用的内存空间为:0;为了区分空对象所占位置,每个空对象都有一个独一无二地址
  2. 当类中有非静态成员变量时,大小即为非静态成员变量的大小;向类中添加静态成员变量或函数,大小都不变
1.3.2this指针概念

this指针指向被调用的成员函数所属的对象

this指针是隐含每一个非静态成员函数内的一种指针

this指针不需要定义,直接使用即可

this指针的用途:

  • 当形参和成员函数同名时,可以用this指针来区分
  • 在类的非静态成员函数中返回对象本身,可以用this

this指针的本质是指针常量,指针的指向是不可以修改的

A *const this

#include<iostream>
using namespace std;

class A
{
public:
	A(int a)
	{
		this->a = a;
	}

	A& add(A &p)       //如果此处不是用引用返回,则每次返回都会创建一个新的对象
	{
		this->a += p.a;
		return *this;
	}

	int a;
};



int main()
{
	A m(10);
	A n(10);
	n.add(m).add(m).add(m);
	cout << n.a << endl;

	system("pause");
	return 0;
}
1.3.3空指针访问成员函数

C++中空指针也是可以使用成员函数的,但是也要注意有没有用到this指针

如果用到this指针,需要加以判断代码的健壮性

即:调用的成员函数不能包含属性等东西,因为this为空,无法显示

#include<iostream>
using namespace std;

class A
{
public:
	void show()
	{
		cout << "打印***" << endl;
	}

	void put()
	{
		//cout << a << endl;    //错误;此处a相当于this->a,this为空,所以无法输出;
	}
private:
	int a;
};

void test()
{
	A *a = NULL;
	a->show();
	a->put();
}

int main()
{
	test();
	system("pause");
	return 0;
}
1.3.4const修饰成员函数

常函数:

  • 成员函数后加const后我们称为这个函数为常函数  (void change_2() const)
  • 常函数内不可修改成员属性
  • 成员函数声明时加关键字mutable后,在常函数中依然可以修改(mutable int b;)

常对象:

  • 声明对象前加const称该对象为常对象(const A q;)
  • 常对象只能调用常函数
#include<iostream>
using namespace std;

class A
{
public:
	void change_1()
	{
		a = 100;
		b = 100;
	}

	//在成员函数后面加const,修饰的是this指针,让指针指向的值也不可以修改
	void change_2() const      //相当于 const A* const this
	{
		//a = 100;     //不可以修改
		b = 100;    //特殊变量,可以修改
	}

	int a;
	mutable int b;    //特殊变量,即使在常函数中也可以修改
};

void test()
{
	A p;
	p.a = 100;
	p.b = 100;
	p.change_1();
	p.change_2();
}

void test2()
{
	const A q;
	//q.a = 100;     //常对象不能修改普通值
	q.b = 100;
	//q.change_1();     //错误,常对象只能调用常函数,因为普通成员函数可以修改属性
	q.change_2();
}

int main()
{
	test();
	system("pause");
	return 0;
}

1.4友元函数

友元的关键字:friend

三种实现形式:

  • 全局函数做友元

即:当全局函数中的对象想要访问类中的私有属性时,需要将该全区函数在类中加以声明为friend类型

class Student
{
//此时全局函数show是类Student的好朋友,可以访问类的私有成员
friend void show(Student &a);          //此时全局函数中的对象都可以访问类中的私有属性
........

#include<iostream>
using namespace std;
#include<string>

class Student
{
	friend void show(Student &a);

public:
	Student()
	{
		age = 18;
		name = "小明";
	}

	string name;

private:
	int age;
};

void show(Student &a)
{
	cout << a.name << endl;
	cout << a.age << endl;
}

int main()
{
	Student m;
	show(m);
	system("pause");
	return 0;
}
  • 类做友元

class Class
{
..........
};

class Student
{
friend class Class;
.........

#include<iostream>
using namespace std;
#include<string>

class Student;

class Class
{
public:
	Class();
	void show();
private:
	Student *s;
};

class Student
{
	friend class Class;
public:
	Student()
	{
		age = 18;
		name = "小明";
	}
	string name;
private:
	int age;
};
Class::Class()
{
	s = new Student;
}

void Class::show()
{
	cout << s->age << endl;
	cout << s->name << endl;
}
int main()
{
	Class a;
	a.show();
	system("pause");
	return 0;

  • 成员函数做友元

class Class
{
..........
};

class Student
{
friend void Class::show();        //和类做友元类似,此处把类变成了函数
.........

#include<iostream>
using namespace std;
#include<string>

class Student;

class Class
{
public:
	Class();
	void show();
	void show1();
private:
	Student *s;
};

class Student
{
	friend void Class::show();
public:
	Student()
	{
		age = 18;
		name = "小明";
	}
	string name;
private:
	int age;
};
Class::Class()
{
	s = new Student;
}

void Class::show()
{
	cout << s->age << endl;        //类的友元函数,可以访问私有属性
	cout << s->name << endl;
}
void Class::show1()
{
//	cout << s->age << endl;       //不能访问私有属性
	cout << s->name << endl;
}
int main()
{
	Class a;
	a.show();
	a.show1();
	system("pause");
	return 0;
}

1.5运算符重载

概念:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型

1.5.1加号运算符重载

实现两个自定义数据类型相加的运算

和正常的相加思路类似,只不过这里需要引用其他的对象作为参数;

    //成员函数实现“+”运算符重载              p1=p2+p3;类似于:p1=p2.operator+(p3);
Class operator+(const Class &p){
Class q(0, 0);
q.a = this->a + p.a;
q.b = this->b + p.b;
return q;
}

//全局函数实现“+”运算符重载             p1=p2+p3;类似于:p1=operator+(p2,p3);
Class operator+(const Class &p1, const Class &p2){
Class q(0,0);
q.a = p1.a + p2.a;
q.b = p1.b + p2.b;
return q;
}

#include<iostream>
using namespace std;
#include<string>

class Class
{
public:
	Class(int a, int b) :a(a), b(b){}
	void show()
	{
		cout<<"a="<< a << "    b=" << b << endl;
	}

	//成员函数实现“+”运算符重载
	Class operator+(const Class &p){
		Class q(0, 0);
		q.a = this->a + p.a;
		q.b = this->b + p.b;
		return q;
	}
//private:
	int a;
	int b;
};

全局函数实现“+”运算符重载
//Class operator+(const Class &p1, const Class &p2){
//	Class q(0,0);
//	q.a = p1.a + p2.a;
//	q.b = p1.b + p2.b;
//	return q;
//}

int main()
{
	Class a(10,10);
	Class b(20, 30);
	Class c(0, 0);
	c= a + b;
	c.show();
	system("pause");
	return 0;
}
1.5.2运算符重载

作用:可以输出自定义数据类型

//只能用全局函数实现“<<”运算符重载
ostream & operator<<(ostream &cout, const Class &p)    //本质operator<<(cout,p),简化cout<<p
{
cout << p.a << "    " << p.b << endl;
return cout;             //此处返回cout的为了能够实现链式编程,可以多次返回输出
}

#include<iostream>
using namespace std;
#include<string>

class Class
{
public:
	Class(int a, int b) :a(a), b(b){}
	

	int a;
	int b;
};

//如果利用成员函数重载 左移运算符 p.operator<<(cout)   简化版本 p<<cout;p在左侧错误

//只能用全局函数实现“<<”运算符重载
ostream & operator<<(ostream &cout, const Class &p)    //本质operator<<(cout,p),简化cout<<p
{
	cout << p.a << "    " << p.b << endl;
	return cout;
}

int main()
{
	Class a(10,10);
	cout << a<<endl;
	system("pause");
	return 0;
}
1.5.3递增运算符重载

作用:通过重载递增运算符,实现自己的整型数据

    //重载前置++运算符
Class &operator++()           //返回引用是为了一直对一个数据进行增值操作
{
a++;
return *this;
}

    //重载后置++运算符          //此时不能再使用链式操作了
Class operator++(int)      //此处int作为占位符,用来区分前置还是后置
{
Class p = *this;    //记录当前本身的值,然后让本身的值加1,但是返回的值还是以前的值
a++;
return p;
}

前置返回的是一个引用,而后置返回的是结果是一个临时对象。因为后置的时候原来的对象已经被++改变了,所以需要一个新对象来保存改变之前的值。而前置用引用则是为了不产生临时变量,减少内存的消耗而已。

所以前置引用可以使用链式的方法,多次前置++,而后置不可以多次++;

#include<iostream>
using namespace std;
#include<string>

class Class
{
	friend ostream & operator<<(ostream &cout, const Class &p);

public:
	Class(int a) :a(a){}
	
	//重载前置++运算符
	Class &operator++()           //返回引用是为了一直对一个数据进行增值操作
	{
		a++;
		return *this;
	}

	//重载后置++运算符
	Class operator++(int)      //此处int作为占位符,用来区分前置还是后置
	{
		Class p = *this;    //记录当前本身的值,然后让本身的值加1,但是返回的值还是以前的值
		a++;
		return p;
	}
private:
	int a;
};


ostream & operator<<(ostream &cout, const Class &p) 
{
	cout << p.a << endl;
	return cout;
}

int main()
{
	Class a(10);
	cout << ++(++a)<<endl;
	cout << a << endl;
	cout << a++ << endl;
	cout << a << endl;
	system("pause");
	return 0;
}
1.5.4赋值运算符重载

赋值运算符需要注意的地方就是当数据在堆区存储时,注意开辟新的空间以及及时释放空间

    void operator = (Class &p)
{
if (a != NULL)          //如果已经有值,需要先释放,再进行赋值
{
delete a;
a = NULL;
}
//深拷贝
a = new int(*p.a);
}

#include<iostream>
using namespace std;
#include<string>

class Class
{
	friend ostream & operator<<(ostream &cout, const Class &p);

public:
	Class(int a){
		this->a = new int(a);
	}
	
	void operator = (Class &p)
	{
		if (a != NULL)
		{
			delete a;
			a = NULL;
		}
		//深拷贝
		a = new int(*p.a);
	}
private:
	int *a;
};


ostream & operator<<(ostream &cout, const Class &p) 
{
	cout << *p.a << endl;
	return cout;
}

int main()
{
	Class a(10);
	Class b(20);
	a = b;
	cout << a << endl;
	system("pause");
	return 0;
}
1.5.5关系运算符重载

作用:可以让两个自定义类型对象进行对比操作

    bool operator == (Class &p)             //相对比较简单,就简单传值,然后对比一下
{
if (this->a == a) {
return true;
}
else {
return false;
} }

#include<iostream>
using namespace std;

class Class
{

public:
	Class(int a){
		this->a = a;
	}
	
	bool operator == (Class &p)
	{
		if (this->a == a)
		{
			return true;
		}
		else
		{
			return false;
		}
	}
private:
	int a;
};

int main()
{
	Class a(10);
	Class b(20);
	if (a == b)
	{
		cout << "两个数相等" << endl;
	}
	else
	{
		cout << "两个数不相等" << endl;
	}
	system("pause");
	return 0;
}
1.5.6函数调用运算符重载
  • 函数调用运算符()也可以重载
  • 由于重载后使用的方法非常像函数的调用,因此称为仿函数
  • 仿函数没有固定的写法,非常灵活

    void operator()(string world)
{
cout << world << endl;
}

#include<iostream>
using namespace std;
#include<string>

class Class
{
public:
	void operator()(string world)
	{
		cout << world << endl;
	}
};

int main()
{
	Class a;
	a("hells world");     //由于使用起来类似于函数调用,因此称为仿函数;非常灵活,没有固定写法
	
	//此处为匿名函数对象
	Class()("hello world");          //Class()代替了类似于a
	system("pause");
	return 0;
}

1.6继承

继承的好处:减少重复代码

继承语法:class A(子类) : 继承方式  B(父类)

子类也称为派生类;父类也称为基类

派生类中的成员,包含两大部分:

  • 从基类继承过来的,表现其共性
  • 自己增加的函数,体现其个性
1.6.1 继承方式

一共有三种:

  • 公有继承     (三种权限不变)
  • 保护继承     (公+保->保,私不变)
  • 私有继承     (公+保->私,私不变)
  • 继承时,私有成员子类均不可访问,保护成员子类可以访问

下面这段代码,详细注释了各种情况下的访问和继承

#include<iostream>
using namespace std;
#include<string>

class Class
{
public:
	int a;
protected:
	int b;
private:
	int c;
};

class sun1:public Class
{
	void fun()
	{
		a = 10;    //公有 不变
		b = 10;    //保护 不变
		//c = 10;  错误,子类不可访问父类的私有成员
	}
};
class sun2 :protected Class
{
	void fun()
	{
		a = 10;     //保护 变为
		b = 10;     //保护 不变
		//c = 10;   错误,子类不可访问父类的私有成员
	}
};
class sun3 :private Class
{
	void fun()
	{
		a = 10;     //私有 变为
		b = 10;     //私有 变为
		//c = 10;   错误,子类不可访问父类的私有成员
	}
};

void test()
{
	sun1 p1;
	p1.a = 10;
	//p1.b = 20;     //保护b不可访问
	//p1.c = 30;     //私有c不可访问

	sun2 p2;
	//p2.a = 10;      //保护a不可访问
	//p2.b = 20;      //保护b不可访问
	//p2.c = 30;      //私有c不可访问

	sun3 p3;
	//p3.a = 10;      //私有a不可访问
	//p3.b = 20;      //私有b不可访问
	//p3.c = 30;      //私有c不可访问

}
int main()
{
	test();
	system("pause");
	return 0;
}
1.6.2继承中的对象模型

继承规则:

  • 父类中所有非静态成员属性都会被子类继承下去
  • 父类中私有成员属性,是被编译器给隐藏了,因此是访问不到的,但是确实是继承下去了

class Class{
public:   int a;
protected:  int b;
private:   int c;
};
class sun1 :public Class{
int a;
};                        //此时sun1定义出的对象大小就为16,继承的三个加上新增加的一个

1.6.3继承中构造和析构顺序

先构造父类,再构造子类,析构的顺序与构造的顺序相反(可以自己写代码实验)

1.6.4继承同名成员处理方式
  • 访问子类同名成员,直接访问即可
  • 访问父类同名成员,需要加作用域

    sun p;
cout << p.a << endl;
cout << p.Base::a << endl;

  • 当子类与父类拥有同名成员函数,子类会隐藏父类中所有版本的同名成员函数
  • 如果想访问父类中隐藏的同名成员函数,需要加父类的作用域

    p.change();    //子类中的成员函数
//p.change(a);    //此处虽然是重载函数,但是只要子类父类发生重名,子类就会隐藏父类所有同名函数
p.Base::change(9);     //父类中的带参成员函数
p.Base::change();      //父类中的不带参成员函数

#include<iostream>
using namespace std;
#include<string>

class Base{
public: 
	Base()
	{
		a = 10;
	}
	void change()
	{
		a = 100;
	}
	void change(int x)
	{
		a = x;
	}
	int a;
};
class sun:public Base{
public:
	sun()
	{
		a = 20;
	}
	void change()
	{
		a = 200;
	}
	int a;
};

void test()
{
	sun p;
	//成员
	cout << p.a << endl;
	cout << p.Base::a << endl;

	//成员函数
	p.change();    //子类中的成员函数
	//p.change(a);    //此处虽然是重载函数,但是只要子类父类发生重名,子类就会隐藏父类所有同名函数
	p.Base::change(9);     //父类中的带参成员函数
	p.Base::change();      //父类中的不带参成员函数
}
int main()
{
	test();
	system("pause");
	return 0;
}
1.6.5继承同名静态成员处理方式

静态成员和非静态成员出现同名,处理方式一致,只不过有两种访问方式

  • 访问子类同名成员,直接访问即可
  • 访问父类同名成员,需要加作用域

    cout << p.a << endl;
cout << p.Base::a << endl;

    cout << sun::a << endl;
cout << sun::Base::a << endl;

1.6.6多继承语法

C++中允许一个类继承多个类

语法: class 子类 :继承方式 父类1,继承方式 父类2........

(实际开发中不建议)

class Base :public Base1, public Base2{}

当父类中出现同名成员,需要加作用域进行区分

    cout << p.a << endl;
cout << p.Base1::a << endl;
cout << p.Base2::a << endl;

#include<iostream>
using namespace std;
#include<string>

class Base1{
public:
	Base1()
	{
		a = 10;
	}
	int a;
};

class Base2{
public:
	Base2()
	{
		a = 20;
	}
	int a;
};

class Base :public Base1, public Base2
{
public:
	Base()
	{
		a = 30;
	}

	int a;
};

void test()
{
	Base p;
	cout << p.a << endl;
	cout << p.Base1::a << endl;
	cout << p.Base2::a << endl;
}

int main()
{
	test();
	system("pause");
	return 0;
}
1.6.7菱形继承

概念:

  1. 两个派生类继承同一个基类
  2. 又有某个类同时继承两个派生类
  3. 这种继承称为菱形继承

如图所示:B1,B2继承A,C又继承B1,B2。

当出现这种零星继承时,如果两个父类拥有相同数据,需要加以作用域区分

这份数据我们知道,只需要一份就足够了,而菱形继承导致数据有两份,资源浪费。

解决方法:

  • 利用虚继承 解决菱形继承的问题
  • 继承之前加上关键字virtual变为虚继承
  • A变为虚基类

class B1 :virtual public A{};
class B2 :virtual public A{};

1.7多态

1.7.1多态的基本概念

多态分为两类:

  • 静态多态:函数重载和运算符重载属于静态多态,复用函数名
  • 动态多态:派生类和虚函数实现运行时多态

静态多态和动态多态的区别:

  • 静态多态的函数地址早绑定-编译阶段确定函数地址
  • 动态多态的函数地址晚绑定-运行阶段确定函数地址

地址早绑定的话,在编译阶段就确定了函数地址,因此函数就不会更改了,一直显示某一个

若要按要求执行,使得函数不提前绑定,就需要用到动态多态,即加上关键字"virtual",变成虚函数

动态多态满足的条件:

  1. 有继承关系
  2. 子类重写父类的虚函数          (重写:函数返回值类型,函数名,参数列表完全一致称为重写)

动态多态的使用:

  1. 父类指针或者引用,执行子类对象
#include<iostream>
using namespace std;
#include<string>

class Base1{
public:
    //虚函数
	virtual void show()
	{
		cout << "Base1" << endl;
	}
};

class Base2:public Base1
{
public:
	void show()
	{
		cout << "Base2" << endl;
	}
};

class Base3 :public Base1
{
public:
	void show()
	{
		cout << "Base3" << endl;
	}
};

void test(Base1 &p)
{
	p.show();
}

int main()
{
	Base1 p1;
	Base2 p2;
	Base3 p3;
	test(p1);
	test(p2);
	test(p3);
	system("pause");
	return 0;
}

1.7.2 纯虚函数和抽象类

因为在多态中,通常父类的虚函数实现毫无意义,通常都是调用子类重写的内容,因此可以将虚函数改为纯虚函数

语法:virtual 返回值类型 函数名(参数列表)=0

当类中有了纯虚函数,这个类也称为抽象类

抽象类特点:

  • 无法实例化对象
  • 子类必须重写抽象类中的纯虚函数,否则也属于抽象类

class Base1{
public:
//纯虚函数,所以此类也被称为抽象类
virtual void show() = 0;
};

则不能Base1 p; 因为Base1是抽象类,抽象类不能实例化对象。

#include<iostream>
using namespace std;
#include<string>

class Base1{
public:
	//纯虚函数,所以此类也被称为抽象类
	virtual void show() = 0;
};

class Base2:public Base1
{
public:
	//重写父类Base1中的show函数
	void show(){ cout << "Base2"; }
};

class Base3 :public Base1
{

};
void test()
{
	//Base1 p;         //抽象类不能实例化对象
	//Base3 p;       //如果不重写抽象类中的纯虚函数,则子类也变成抽象类
	Base2 p;
	p.show();
}

int main()
{
	test();
	system("pause");
	return 0;
}

1.7.3虚析构和纯虚析构

多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码

解决方法:将父类中的析构函数改为虚析类或者纯虚析构

  • 如果子类中没有堆区数据,可以不写虚析构或者纯虚函数

虚析类和纯虚析构共性:

  • 可以解决父类指针释放子类对象
  • 都需要有具体的函数实现

虚析类和纯虚析构区别:

  • 如果是纯虚析构,该类属于抽象类,无法实例化对象

虚析构语法:

virtual ~类名(){}

纯虚析构语法:

virtual ~类名()=0

    //利用虚析构可以解决父类释放子类对象时堆区数据泄露的问题
virtual ~Base1()
{
cout << "Base1的析构函数" << endl;
}

    //纯虚析构  需要声明也需要实现
//有了纯虚析构,这个类也属于抽象类,无法实例化对象
virtual ~Base1() = 0;

#include<iostream>
using namespace std;
#include<string>

class Base1{
public:
	Base1()
	{
		cout << "Base1的构造函数" << endl;
	}

	利用虚析构可以解决父类释放子类对象时堆区数据泄露的问题
	//virtual ~Base1()
	//{
	//	cout << "Base1的析构函数" << endl;
	//}

	//纯虚析构  需要声明也需要实现
	//有了纯虚析构,这个类也属于抽象类,无法实例化对象
	virtual ~Base1() = 0;
};
Base1::~Base1()
{
	cout << "Base1纯虚析构函数" << endl;
}

class Base2:public Base1
{
public:
	Base2()
	{
		cout << "Base2的构造函数" << endl;
	}
	~Base2()
	{
		cout << "Base2的析构函数" << endl;
	}
};

void test()
{
	Base1 *p = new Base2;
	//父类指针在析构时候,不会调用子类中析构函数,导致子类如果有堆区数据,会出现内存泄露
	delete p;
}

int main()
{
	test();
	system("pause");
	return 0;
}

2,文件操作

程序运行时产生的数据都属于临时数据,程序一旦运行结束都会被释放

通过文件可以将数据持久化,需要包含的头文件

文件类型分为两种:

  • 文本文件:文件以文本的ASCLL码形式存储在计算机中
  • 二进制文件:文件以文本的二进制形式存储在计算机中,用户一般不能直接读懂他们

操作文件的三大类

  • ofstream:写操作
  • ifstream:读操作
  • fstream:读写操作

2.1文本文件

2.1.1写文件

步骤:

  1. 包含头文件:#include

  2. 创建流对象:ofstream test;

  3. 打开文件:test.open("文件路径",打开方式);

  4. 写数据:test<<"写入的数据";

  5. 关闭文件:test.close();

写在最后

学习技术是一条慢长而艰苦的道路,不能靠一时激情,也不是熬几天几夜就能学好的,必须养成平时努力学习的习惯。所以:贵在坚持!

最后再分享的一些BATJ等大厂20、21年的面试题,把这些技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,上面只是以图片的形式给大家展示一部分。

蚂蚁金服三面直击面试官的Redis三连,Redis面试复习大纲在手,不慌

Mybatis面试专题

蚂蚁金服三面直击面试官的Redis三连,Redis面试复习大纲在手,不慌

MySQL面试专题

蚂蚁金服三面直击面试官的Redis三连,Redis面试复习大纲在手,不慌

并发编程面试专题

相关阅读docs.qq.com/doc/DSmxTbFJ1cmN1R2dB