【C++】面向对象之封装篇(下)
文章目录
- 二、封装篇(下)
- 4.1 对象数组与对象成员
- (1)对象数组
- (2)对象成员
- 4.2 深拷贝与浅拷贝
- 【栗子1】成员变量没有指针
- 【栗子2】成员变量多了一个指针
- 【深浅拷贝代码实践】
- 4.3 对象指针、对象成员指针
- (1)对象指针
- (2)对象指针代码实践
- (3)对象成员指针
- (4)内存中的对象成员指针
- (5)对象成员指针代码实践
- 4.4 this指针
- 4.5 const进阶
- 4.6 常指针和常引用
- (1)对象的引用和对象的指针
- (2)对象的常引用和常指针
- Reference
二、封装篇(下)
4.1 对象数组与对象成员
(1)对象数组
很多时候我们不止需要一个对象,而是一组对象,如一个班50个学生,那就可以使用对象数组。
【对象数组代码实践】
题目:定义一个坐标(Coordinate
)类,其数据成员包含横坐标和纵坐标,分别从栈和堆中实例化长度为3的对象数组,给数组中的元素分别赋值,最后遍历两个数组。
头文件:
class Coordinate{public:Coordinate();~Coordinate ();
public:int m_iX;int m_iY;
};
源程序:
#include<iostream>
#include<stdlib.h>
#include"Coordinate.h"using namespace std;/* 对象数组
/* 要求1. 定义Coordiante类2. 数据成员:m_iX、m_iY3. 分别从栈和堆中实例化长度为3的对象数组4. 给数组中的元素分别赋值5. 遍历两个数组
/* *****************************************/Coordinate::Coordinate(){cout <<"Coordinate()"<<endl;
}Coordinate::~Coordinate (){cout <<"~Coordinate()"<< endl;
}int main(){Coordinate coor[3]; //从栈上实例化对象数组coor[0].m_iX =3;coor[0].m_iY =5;Coordinate *p =new Coordinate[3];p->m_iX = 7; //直接写p的话,就说明是第一个元素p[0].m_iY =9; //等价于 p->m_iY = 9p++; //将指针后移一个位置,指向第2个元素p->m_iX = 11;p[0].m_iY = 13; //这里p指向的是第二个元素,p[0]就是当前元素,等价于p->m_iY = 13p[1].m_iX = 15;//第3个元素的横坐标p++; //将指针后移一个位置,指向第3个元素p[0].m_iY = 17;//这里p指向的是第三个元素,p[0]就是当前元素,等价于p->m_iY = 17for(int i = 0; i < 3; i++){cout <<"coor_X: "<< coor[i].m_iX <<endl;cout <<"coor_Y: "<< coor[i].m_iY <<endl;}for(int j = 0; j < 3; j++){//如果上面p没有经过++操作,就可以按下面来轮询//cout <<"p_X: " << p[i].m_iX <<endl;//cout <<"p_Y: " << p[i].m_iY <<endl;//但是,上面我们对p做了两次++操作,实际p已经指向了第3个元素,应如下操作cout <<"p_X: "<< p->m_iX <<endl;cout <<"p_Y: "<< p->m_iY <<endl;p--;}//经过了三次循环后,p指向了一个非法内存,不能直接就delete,而应该让p再指向我们申请的一个元素的,如下p++; //这样p就指向了我们申请的内存delete []p;p = NULL;system("pause");return 0;
}
结果 :
Coordinate()
Coordinate()
Coordinate()
Coordinate()
Coordinate()
Coordinate()
coor_X: 3
coor_Y: 5
coor_X: -858993460
coor_Y: -858993460
coor_X: -858993460
coor_Y: -858993460
p_X: 15
p_Y: 17
p_X: 11
p_Y: 13
p_X: 7
p_Y: 9
~Coordinate()
~Coordinate()
~Coordinate()
请按任意键继续. . .
从运行结果来看,首先看到的是打印出六行“Coordinatre()
”,这是因为分别从栈实例化了长度为3的对象数组和从堆实例化了长度为3的对象数组,每实例化一个对象就要调用一次默认构造函数。
最后只打印出三行“~Coordinate()
”,那是不是只是从堆上实例化的对象销毁时调用了析构函数,而从栈实例化的对象销毁时,也有调用函数
——从栈实例化的对象在销毁时,系统自动回收内存,即自动调用析构函数,只是当我们按照提示“请按任意键结束”,按下任何键后,屏幕会闪一下,就在这闪的过程中,会出现三行“~Coordinate()
”的字样,只是我们不容易看到而已。
(2)对象成员
上面说的类的数据成员都是基本的数据类型,比如汽车类,我们只声明了汽车轮子个数,显然还不够,因为轮子本身就是一个对象,汽车上还有发动机、座椅等等对象。
如下的直角坐标,起点A和终点B,要定义这样的线段类和点坐标类
【对象成员代码实践 】
定义两个类:
(1)坐标类:Coordinate
数据成员:横坐标m_iX
,纵坐标m_iY
成员函数:构造函数、析构函数,数据成员的封装函数
(2)线段类:Line
数据成员:点A m_coorA
,点B m_coorB
成员函数:构造函数,析构函数,数据成员的封装函数,信息打印函数
头文件Coordinate.h
:
class Coordinate{public:Coordinate();~Coordinate();void setX(int x);int getX();void setY(int y);int getY();
private:int m_iX;int m_iY;
};
头文件Line.h
:
#include "Coordinate.h"class Line{public:Line();~Line();void setA(int x, int y);void setB(int x, int y);void printInfo();
private:Coordinate m_coorA;Coordinate m_coorB;
};
源程序Coordinate.cpp
:
#include <iostream>
#include "Coordinate.h"
using namespace std;Coordinate::Coordinate (){cout <<"Coordinate()"<<endl;
}Coordinate::~Coordinate (){cout <<"~Coordinate()"<<endl;
}
void Coordinate::setX(int x){m_iX = x;
}
int Coordinate::getX(){return m_iX;
}
void Coordinate::setY(int y){m_iY = y;
}
int Coordinate::getY(){return m_iY;
}
源程序Line.h
:
#include<iostream>
#include "Line.h"
//#include "Coordinate.h"using namespace std;Line::Line(){cout <<"Line()"<< endl;
}
Line::~Line(){cout <<"~Line()"<< endl;
}
void Line::setA(int x, int y){m_coorA.setX(x);m_coorA.setY(y);
}
void Line::setB(int x, int y){m_coorB.setX(x);m_coorB.setY(y);
}
void Line::printInfo(){cout << "(" << m_coorA.getX() <<","<< m_coorA.getY()<< ")" <<endl;cout << "(" << m_coorB.getX() <<","<< m_coorB.getY()<< ")" <<endl;
}
源程序demo.cpp
:
//我们首先来实例化一个线段类的对象,如下
#include <iostream>
#include "Line.h"using namespace std;int main(){Line *p = new Line();delete p;p = NULL;system("pause");return 0;
}
从运行结果来看,先连续调用了两次坐标类的构造函数,再调用了一次线段类的构造函数,这就意味着先创建了两个坐标类的对象,这两个坐标类的对象就是A点和B点,然后才调用线段这个对象,线段这个对象是在A点和B点初始化完成之后才被创建。
而在销毁时,先调用的是线段类的析构函数,然后连续调用两次坐标类的析构函数。可见,对象成员的创建与销毁的过程正好相反,也验证了我们之前给出的结论。
作为一条线段来说,我们希望的是,在这条线段创建的时候就已经将线段的起点和终点确定下来。为了达到这个目的,我们往往希望线段这个类的构造函数是带有参数的,并且这个参数将来能够传给这两个点,所以可以进一步完善这个程序。
4.2 深拷贝与浅拷贝
在封装(上)中学习了拷贝构造函数的声明方法和自动调用的时间,但是如何实现拷贝构造函数呢?分为深拷贝和浅拷贝。
【栗子1】成员变量没有指针
- 上面栗子中:定义的一个数组的类(Array)中定义了一个数据成员(
m_iCount
),并且定义了构造函数,在其中对数据成员赋了初值5。 - 另外还定义了一个拷贝构造函数。在这个拷贝构造函数是这样实现的:传入的参数是
arr
,这个参数的数据类型也是Array
类对象,所以其肯定也含有数据成员m_iCount
,这里将arr
的数据成员m_iCount
赋值给本身的m_icount
。 - 当我们使用时,先用
Array arr1
来实例化一个arr1
的时候,就会调用到arr1的构造函数,也就是说将arr1中的数据成员m_icount
赋了初值5。 - 而我们使用
Array arr2 = arr1
的时候,也就是用arr1
去初始化arr2
——这时实例化arr2
的时候就会调用到它的拷贝构造函数,拷贝构造函数中的参数arr
其实就是arr1
,里面代码实现的时候,就相当于将arr1
的数据成员m_icount
赋值给arr2
的数据成员m_icount
。
【栗子2】成员变量多了一个指针
在这个例子中,我们新加了一个数据成员,它是int
型的指针m_pArr
,其在构造函数中,从堆中申请了一段内存,并且指向了申请的这段内存,内存的大小就是m_icount
。
(1)当我们使用时,先用Array arr1
来实例化一个arr1
的时候,就会调用到arr1的构造函数,也就是说将arr1中的数据成员m_icount
赋了初值5。
(2)而我们使用Array arr2 = arr1
的时候,也就是用arr1去初始化arr2,这时实例化arr2的时候就会调用到它的拷贝构造函数,于是就将arr1的数据成员m_icount
赋值给arr2的数据成员m_icount
,将arr1的数据成员m_pArr
赋值给arr2的数据成员m_pArr
。
在这两个例子中,有共同的特点,那就是,只是将数据成员的值作了简单的拷贝,我们就把这种拷贝模式称为浅拷贝。
但是对于第一个例子来说,使用浅拷贝的方式来实现拷贝构造函数并没有任何问题,而对于第二个例子来说,肯定是有问题的——经过浅拷贝之后,对象arr1中的指针和对象arr2中的指针势必会指向同一块内存(因为我们将arr1的数据成员
m_pArr
赋值给arr2的数据成员m_pArr
),这里假设指向的地址是0x00FF00(如下图所示)。
- 在这个时候,如果我们先给arr1的
m_pArr
赋了一些值,也就是说在这段内存中就写了一些值,然后我们再给arr1的m_pArr
去赋值的时候,这段内存就会被重写,而覆盖掉了之前给arr1的m_pArr
所赋的一些值。 - 更严重的问题是,当我们去销毁arr1这个对象的时候,我们为了避免内存泄漏,肯定会释放掉
m_pArr
所指向的这段内存。如果我们已经释放掉了这段内存,我们再去销毁arr2这个对象时,我们肯定也会以同样的方式去释放掉arr2中m_pArr
这个指针所指向的这段内存,那么就相当于,同一块内存被释放了两次,导致报错。 - 所以我们希望这里的拷贝构造函数所完成的工作,两个对象的指针所指向的应该是两个不同的内存,拷贝的时候不是将指针的地址简单的拷贝过来,而是将指针所指向的内存当中的每一个元素依次的拷贝过来。(如下图)
为了实现刚才的效果,如下修改:
这段代码与之前的代码的区别在于其拷贝构造函数,其中的m_pArr
不是直接赋值arr中的m_pArr
,而是先分配一段内存(PS:这段内存分配成功与否,这里没有判断,因为这个不是这里要讲的重点),重点是后面的一段for循环语句。我们应该将arr中的m_pArr
的每一个元素都拷贝到当前的m_pArr
所指向的相应的内存当中去。
总结:当进行对象拷贝时,不是简单的做值的拷贝,而是将堆中内存的数据也进行了拷贝(深拷贝)。
【深浅拷贝代码实践】
- 定义一个
Array
类。- 数据成员:
m_iCount
- 成员函数:构造函数、拷贝构造函数,析构函数
- 数据成员的封装函数
- 要求通过这个例子体会浅拷贝原理
- 数据成员:
头文件Array.h
class Array{public://构造函数Array();//拷贝构造函数Array(const Array &arr);~Array();void setCount(int count);int getCount();
private://唯一个成员变量int m_iCount;
};
而该Array.cpp
源程序为:
#include"Array.h"
#include<iostream>
using namespace std;Array::Array(){cout <<"Array()"<< endl;
}
//拷贝构造函数
Array::Array(const Array &arr){m_iCount = arr.m_iCount;cout <<"Array(const Array &arr)"<<endl;
}
Array::~Array(){cout <<"~Array()"<< endl;
}
void Array::setCount(int count){m_iCount = count;
}
int Array::getCount(){return m_iCount;
}
main.cpp
源程序为:
#include<iostream>
#include<stdlib.h>
#include"Array.h"using namespace std;int main(){Array arr1;arr1.setCount(5);//通过Arr1来实例化arr2Array arr2(arr1); cout <<"arr2.m_iCount"<<" "<< arr2.getCount() << endl;system("pause");return 0;
}
- 在1的基础上增加一个数据成员:
m_pArr
- 并增加
m_pArr
地址查看函数 - 同时改造构造函数、拷贝构造函数和析构函数
- 要求通过这个例子体会深拷贝的原理和必要性
- 并增加
修改后的栗子如下:
Array.h
增加数据成员m_iCount
:
class Array{public://构造函数Array(int count);//拷贝构造函数Array(const Array &arr);~Array();void setCount(int count);int getCount();//新增查看地址函数void printAddr();
private://唯一个成员变量int m_iCount;//新增数据成员:m_pArrint *m_pArr;
};
源程序Array.cpp
,注意这里我们还是先让拷贝构造函数是用浅拷贝试试:
#include"Array.h"
#include<iostream>
using namespace std;Array::Array(int count){m_iCount = count;//堆上分配内存m_pArr = new int[m_iCount];
}
Array::Array(const Array &arr){m_iCount = arr.m_iCount;m_pArr = arr.m_pArr;//这里先用浅拷贝实现方式来看看会有什么后果?cout <<"Array(const Array &arr)"<<endl;
}
//析构函数
Array::~Array(){//delete掉数组指针delete []m_pArr;m_pArr = NULL;cout <<"~Array()"<< endl;
}
void Array::setCount(int count){m_iCount = count;
}
int Array::getCount(){return m_iCount;
}
void Array::printAddr(){cout <<"m_pArr的值是:"<< m_pArr << endl;
}
源程序main.cpp
,这里还是通过Arr1来实例化arr2:
#include<iostream>
#include<stdlib.h>
#include"Array.h"using namespace std;int main(){//Array arr1;//修改后的栗子Array arr1(5);//这次删除这句//arr1.setCount(5);//通过Arr1来实例化arr2Array arr2(arr1); //cout <<"arr2.m_iCount"<<" "<< arr2.getCount() << endl;cout << "arr1中";arr1.printAddr();cout << "arr2中";arr2.printAddr();system("pause");return 0;
}
显然,可以发现拷贝构造函数执行后,arr1
和arr2
的m_pArr
值相同,即两个指针指向同一个地址(内存),而在析构时就会执行两次对同一块内存的释放操作,导致报错。
之所以上面木有报错,是因为程序最后加上了system("pause");
如果继续按任意键,就会如下的报错,即只执行了一次析构函数~Array()
。
深拷贝的方式则需要在拷贝构造函数中给当前的这个指针先分配一段内存,然后将传入的对象的对应位置的内存拷贝到新申请的这段内存中区。改为深拷贝后的代码(只需要修改构造函数,和拷贝构造函数):
Array::Array(int count)
{m_iCount = count;m_pArr = new int[m_iCount];for(int i =0; i < m_iCount; i++){m_pArr[i] = i;}cout <<"Array()"<< endl;
}
Array::Array(const Array &arr)
{m_iCount = arr.m_iCount;m_pArr = new int[m_iCount];for(int i = 0; i < m_iCount; i++){m_pArr[i] = arr.m_pArr[i];}cout <<"Array(const Array &arr)"<< endl;
}
并且这次按任意键后,程序也没奔溃:
4.3 对象指针、对象成员指针
(1)对象指针
定义了一个坐标的类(Coordinate
),其有两个数据成员(一个表示横坐标,一个表示纵坐标)。当我们定义了这个类之后,我们就可以去实例化它了。如果我们想在堆中去实例化这个对象呢,就要如下所示:
通过new运算符实例化一个对象后(这个对象就会执行它的构造函数),而对象指针p就会指向这个对象。我们的重点是要说明p与这个对象在内存中的相关位置以及它们之间的对应关系。
当我们通过这样的方式实例化一个对象后,它的本质就是在内存中分配出一块空间,在这块空间中存储了横坐标(m_iX
)和纵坐标(m_iY
),此时m_iX的地址与p所保存的地址应该是一致的,也就是说p所指向的就是这个对象的第一个元素(m_iX
)。如果想用p去访问这个元素,很简单,就可以这样来访问(p -> m_iX
或者p -> m_iY
),也可以在p前加上*,使这个指针变成一个对象,然后通过点号(.)来访问相关的数据成员(如(*p).m_iY
)。
注意:这里的new
运算符可以自动调用对象的构造函数,而C语言中的malloc
则只是单纯的分配内存而不会自动调用构造函数。
(2)对象指针代码实践
定义Coordinate
类:
- 数据成员:
m_iX
和m_iY
- 声明对象指针,并通过指针操控对象
- 计算两个点,横、纵坐标的和
(3)对象成员指针
对象成员,就是作为一个对象来说,它成为了另外一个类的数据成员。而对象成员指针呢,则是对象的指针成为了另外一个类的数据成员了。
(4)内存中的对象成员指针
当实例化line这个对象的时候,那么两个指针(m_pCoorA
和m_pCoorB
)也会被定义出来,由于两个指针都是指针类型,那么都会占4个基本内存单元。如果我们在构造函数当中,通过new
这样的运算符从堆中来申请内存,实例化两个Coordinate
这样的对象的话呢,这两个Coordinate
对象都是在堆中的,而不在line这个对象当中,所以刚才我们使用sizeof的时候呢,也只能得到8,这是因为m_pCoorA
占4个基本内存单元,m_pCoorB
占4个基本内存单元,而右边的两个Coordinate
对象并不在line这个对象的内存当中。当我们销毁line
对象的时候呢,我们也应该先释放掉堆中的内存,然后再释放掉line
这个对象。
(5)对象成员指针代码实践
定义两个类:
坐标类:Coordinate
数据成员:m_iX
和m_iY
成员函数:构造函数、西沟函数、数据成员封装函数
线段类:Line
数据成员:点A指针 m_pCoorA
,点B指针m_pCoorB
成员函数:构造函数、析构函数、信息打印函数
这里和以前的变化,即将Line
类中的成员之前是Coordinate
类对象,这里是Coordinate
类对象指针。
#include"Coordinate.h"class Line
{public:Line(int x1, int y1, int x2, int y2);~Line();void printInfo();
private:Coordinate *m_pCoorA;Coordinate *m_pCoorB;
};
4.4 this指针
this指针就是指向其自身数据的指针。
C++中的每一个对象都能通过this
指针访问自己的地址,this
指针是所有成员函数的隐含参数,所以在成员函数内部,可以用来指向调用对象。
PS:友元函数没有this
指针,因为友元函数不是类的成员,只有成员函数才有this
指针。
#include <iostream>using namespace std;class Box{public:// 构造函数定义Box(double l=2.0, double b=2.0, double h=2.0){cout <<"Constructor called." << endl;length = l;breadth = b;height = h;}double Volume(){return length * breadth * height;}int compare(Box box){return this->Volume() > box.Volume();}private:double length; // Length of a boxdouble breadth; // Breadth of a boxdouble height; // Height of a box
};int main(void){Box Box1(3.3, 1.2, 1.5); // Declare box1Box Box2(8.5, 6.0, 2.0); // Declare box2if(Box1.compare(Box2)){cout << "Box2 is smaller than Box1" <<endl;}else{cout << "Box2 is equal to or larger than Box1" <<endl;}return 0;
}
【this指针代码实践】
定义一个Array类:
数据成员:m_iLen
表示数组长度
成员函数:构造函数 析构函数 m_iLen
的封装函数
信息打印函数printInfo
4.5 const进阶
const的部分可以回顾 【C++基础】引用的用法、const常量引用。
4.6 常指针和常引用
(1)对象的引用和对象的指针
先看一个下面的栗子,类中三个成员函数,其中printInfo()函数是一个常成员函数。那么在实现的时候,也需要在printInfo函数后面加上const关键字来修饰:
class Coordinate{public:Coordinate(int x, int y);int getX();int getY();//常成员函数void printInfo() const;
private:int m_iX;int m_iY;
};
在成员函数的定义后面加上const
:
1.c++的一个机制,让该函数的权限为只读,也就是说它没法去改变成员变量的值。
2.同时,如果一个对象为const,它只有权利调用const函数,因为成员变量不能改变。
比如定义了一个类FooClass
:
class FooClass{public:void Foo(){ /*...*/}
private:/*...*/
};//在程序中创建了一个FooClass的常量对象A,并试图调用这个成员函数Foo()
const FooClass A;
A.Foo();
对A调用成员函数Foo()将会出错!
A是一个const的对象,但是Foo()只能用于非const的对象。定义成员函数Foo()时,显然不能把调用它的对象写到形参列表里面去声明为一个const,比如Foo(const FooClass* this)。怎么让Foo()能用于const对象呢?就是给Foo()加上一个const声明,如下:
class FooClass{public:void Foo() const{ /*...*/}
private:/*...*/
};
这个时候,对前面创建的const的A就可以调用这个成员函数了。
实际上,成员函数Foo()有一个隐式的形参this,它是自身对象的一个指针,但是不能显式地使用在Foo()的形参列表里。加上const就说明,this指向的对象是const对象。
当然,加上了const声明的成员函数,不能对调用它的对象内的成员进行修改(声明为mutable的成员例外)。
(2)对象的常引用和常指针
Reference
[1] https://www.zhihu.com/question/27860418
【C++】面向对象之封装篇(下)相关推荐
- python 面向对象(进阶篇)
上一篇<Python 面向对象(初级篇)>文章介绍了面向对象基本知识: 面向对象是一种编程方式,此编程方式的实现是基于对 类 和 对象 的使用 类 是一个模板,模板中包装了多个" ...
- C++类与对象之封装篇
C++类与对象之封装篇 一 . 封装的意义 封装的意义一 封装的意义二 二 . struct和class的区别(访问权限) 三 . 成员属性一般设置为私有(权限) 一 . 封装的意义 封装是C++面向 ...
- 面向对象编程(C++篇1)——引言
文章目录 1. 概述 2. 详论 2.1. 类与对象 2.2. 数据类型 3. 目录 1. 概述 现代C++与最原始的版本已经差不多是两种不同的语言了.不断发展的C++标准给C++这门语言带来了更多的 ...
- python面向对象编程 -- 封装、继承
面向对象编程 -- 封装.继承 面向对象编程三要素:封装.继承和多态.本文主要看和封装.继承相关的概念:在python中多态的概念比较模糊,本文不做讨论. 1 封装 封装:将数据和操作组装到一起,对外 ...
- 我的第一个ASP类(显示止一篇下一篇文章)
面向对象是现今编程语言的潮流,不过,ASP对面向对象的支持可是寒碜地很.现在感觉ASP的类也不过是一堆函数而已. 不过,在学校时没有学过面向对象的语言,我对面向对象的认识仍然是从ASP开始的. 记下我 ...
- Python 面向对象(初级篇) 2015/09/04 · 基础知识 · 2 评论 · 面向对象 分享到: 24 原文出处: 武沛齐 cnblog Python 面向对象(初级篇) 概述
概述 面向过程:根据业务逻辑从上到下写垒代码 函数式:将某功能代码封装到函数中,日后便无需重复编写,仅调用函数即可 面向对象:对函数进行分类和封装,让开发"更快更好更强-" 面向过 ...
- python面向对象编程(封装与继承)
一. 面向过程编程语言 "面向过程"(Procedure Oriented)是一种以过程为中心的编程思想.分析出解决问题所需要的步 骤,然后用函数把这些步骤一步一步实现,使用的时候 ...
- Python 面向对象(初级篇)
概述 面向过程:根据业务逻辑从上到下写垒代码 函数式:将某功能代码封装到函数中,日后便无需重复编写,仅调用函数即可 面向对象:对函数进行分类和封装,让开发"更快更好更强..." 面 ...
- 面向对象编程其实很简单——Python 面向对象(初级篇)
在Python教学中发现,很多同学在走到面向对象编程这块就开始蒙圈了,为了帮助大家更好的理解面向对象编程并其能将其用到自己的开发过程中,特写此文. 概述 面向过程:根据业务逻辑从上到下写垒代码 函数式 ...
最新文章
- Pandas批量删除dataframe列名中的前缀实战:使用lstrip函数批量删除列名中的前缀(prefix)、使用replace函数批量删除列名中的前缀(prefix)
- 课题背景 一二三代测序技术
- elementui select组件选中后无法自动刷新更新值渲染到页面中
- win2003 IIS6,能访问html页 但是不能访问aspx页解决办法汇总
- java 数据 权限_Java如何利用Mybatis进行数据权限控制详解
- agc015F - Kenus the Ancient Greek(结论题)
- list取值_Redis中List及quicklist实现-2
- Java高级特性——反射
- Linux虚拟文件系统(概述)
- 区块链技术让食品安全体系更完善
- SVN客户端下载及安装
- java进阶--深入理解Java自动装箱拆箱机制(Autoboxing and unboxing)
- 2023 年值得关注的十大人工智能趋势,还不快码住来看!
- Vue路由守卫(拦截)
- 视频教程-Python数据分析(统计分析)视频教程-机器学习
- dw网页制作入学教程_网站制作之dreamweaver入门
- Spring的AOP实现
- jsp+servlet学子商城项目--servlet、dao层的各项练习
- 常用的jar包下载网站
- 计算机毕业论文附录的模板,毕业论文附录的格式和写法
热门文章
- 对python的认识作文500字_关于启示的作文500字
- 2019 香(shen)港(zhen)Regional补题
- 餐厅小票打印模板_新国都KD90:会说话的全功能云打印机
- 1121. Damn Single
- 模具腐蚀皮纹工艺原理及其流程
- laravel连接mysql连接数过多_Laravel 使用Voyager导致多个数据库连接总是返回默认连接?...
- 【等待事件】序列等待事件总结(enq: SQ - contention、row cache lock、DFS lock handle和enq: SV - contention)...
- 【Java开发岗:SpringCould篇】
- SPARQL查询语句
- swift导入oc第三方库