C++基础语法(继承)

news2024/11/26 17:43:40

终于,经过一路的过关斩将,我们来到了继承面前。还记得在最初学习类于对象时,那个对封装概念一直模糊不清的自己,还记得被模板,被迭代器折磨的日日夜夜吗?这一路你挺过来了,你失去了一些东西,也得到一些东西,这一路,你成长了许多。不过,少年,这还不够,还不能够停下脚步,前方无疑会有更多的山,或有荆棘,或有河流,但不要怕,热爱会为你铸就铠甲,坚持会化为你的风帆

目录

什么是继承

继承关系与访问限定符

父子类之间的赋值转换

子类对成员变量的使用

子类对成员方法的使用 

子类的构造和析构函数

构造 

拷贝构造 

赋值重载

析构

继承与友元

继承与类的静态成员 

多继承&菱形继承

继承与组合 


什么是继承

继承(inheritance)是类的三大特性之一,继承机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类

继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,继承是类设计层次的复用

继承的概念并不难理解,接下来我们举个例子来看看,继承是如何实现代码的复用的。拿日常生活中玩过的植物大战僵尸这款游戏来说,假设我们现在要自己写代码实现植物大战僵尸这款游戏,那么我们需要把植物给定义好,定义植物就是把植物的各种特性给抽象成类嘛,比如植物的名字,植物的血量,植物的价格,植物是否具有攻击性,植物的攻击范围等等,我们就拿向日葵,豌豆射手和食人花来举例

class peashooter {
public: 
	string _name = "peashooter";    //豌豆射手
	int _price = 100;               //价格
	int _blood = 100;               //血量
	bool if_attack = true;          //能否攻击
	int hurt_value = 20;            //攻击值
	int attack_range = 200;         //攻击范围
};


//向日葵
class sunflower {
public:
	string _name = "sunflower";
	int _price = 50;
	int _blood = 100;
	bool if_attack = false;
};


//食人花
class corpse_flower {
public:
	string _name = "corpse_flower";
	int _price = 150;
	int _blood = 100;
	bool if_attack = true;
	int hurt_value = 100;            
	int attack_range = 20;         
};

大家有没有发现,每个植物类内部都有名称, 价格,血量,是否能攻击等变量值,这就导致代码的冗余,如果继续给植物类添加更多的属性,那么可想而知,植物类重复的代码量将会变得很大,不仅占用内容,还影响阅读代码

如果能够用一个类,把每个植物类中相同的部分给提取出来,比如植物的名称,植物的价格,植物的血量等,作为一个父亲类

然后每设计一个植物类,都算作这个父亲类的孩子,每个孩子类都可以传承父亲类的成员变量和成员方法,如此以来,孩子类就不需要自己写植物的名称,植物的价格等这些可以从父亲类那传承过来的成员变量了,孩子类只需要在父亲类的基本功能上完成自己所需要扩展的功能就可以了,这样孩子类的代码量就会大大的较少,代码阅读起来更加清晰明了

class plant {
public:
	string _name;    
	int _price;              
	int _blood;              
	bool if_attack;               
};

我们设想的植物类创建完毕之后,我们就让其它的植物去继承这个植物类,只要孩子类继承了父类,那么就拥有了父类的成员变量和方法

继承的语法格式为:  class 子类名: 以何种方式继承  被继承的父类名

如下述代码,豌豆射手,向日葵,食人花都以public方式继承,继承plant类,继承植物类的共有属性之后,就不需要去重复定义植物名,价格,血量等这些变量

class plant {
public:
	string _name;    
	int _price;              
	int _blood;              
	bool if_attack;               
};

class peashooter:public plant {
public:
	
	int hurt_value = 20;            
	int attack_range = 200;         
};


//向日葵
class sunflower: public plant {
public:
};


//食人花
class corpse_flower: public plant {
public:

	int hurt_value = 100;
	int attack_range = 20;
};

如此以来,各个植物其内部的代码量就大大减少,看起来清爽多了 

这种功能在C++里就叫做继承,我们可以说成父亲类与孩子类,孩子类可以继承父亲类的成员函数和成员变量,也可以说成基类与派生类,派生类就是在基类的基础上扩展的结果,这两种概念都一样,用哪个都可以,本篇文章将采用父类与孩子类的叫法

上面提到了继承方式,子类直接继承父类不就行了吗?为什么还搞出来一个继承方式呢?

继承关系与访问限定符

继承关系和访问限定符一样都是有三种,访问限定符的作用是限制类外成员对类内成员变量和方法的访问,在刚学类时,主要介绍了public和private,简单的提了一下protected,在public区域内的成员变量和方法可以被外界调用,在protected区域及private区域内的成员只能在类中访问,类外无法访问,但protected和private的区别在哪里呢?先不急着回答这个问题,我们先看看继承的三种形式

访问限定符是为了保护类内成员,保障封装性。那继承为什么也分为三种呢?这是因为我们要考虑子类继承父类的成员之后,放到哪里的问题?子类也是有public区域,protected区域,private区域的,我子类从你父类那继承成员以后,该放到我子类的哪个区域里呢?

你可能会说对应着放呗,从父类public区域继承过来的就放到子类的public区域,从父类protected区域继承过来的就放到子类的protected区域等等等

但是现在,我因为实际需要,必须把从父类继承过来的成员都放到protected区域,或者都放到private区域,这个时候对应着放就没有办法满足了

我们把前面的类拿出来举个例子演示这三种继承

class plant {
public:
	string _name;    
	int _price;

protected:
	int _blood;    

private:
	bool if_attack;               
};

class sunflower: public plant {
};

如果sunflower以public方式继承plant,那么sunflower将从plant继承到的public成员放到自己的public区域,从protected区域继承到的成员放到自己的protected区域,也就是我们上面说的一一对应的形式存放

如果sunflower以protected方式继承plant,那么sunflower将从plant继承到的public成员放到自己的protected区域,从protected区域继承到的成员也放到自己的protected区域

如果sunflower以private方式继承plant,那么sunflower将从plant继承到的public成员放到自己的private区域,从protected区域继承到的成员也放到自己的private区域

各位同学,不知道你们是否发现,我从来没说过从plant继承到的private成员放到sunflower的哪个区域,这是因为被private修饰的成员无法被继承

到这里可以跟大家解释protected和private有什么区别了,从类外部访问类内部来看,protected和private的作用是一样的,都是防止类外访问类内成员,维护封装

两者的区别体现在继承上,位于private区域的成员,不能被子类所继承,只有位于public和protected区域的成员可以被继承(这里说private成员不能被继承不太准确,因为查看子类的数据类型,发现其确实被继承下来了,但是子类不可见,故而说成无法继承)

这样做是考虑到父类的某些成员不想被子类所继承,同时不想被外界访问,那就可以放到private区域

父类的某些成员如果想被子类所继承,但是不想被外界访问,就可以放到protected区域里

表面上看C++提供那么多种继承方式,但实用的没几个,如下是总结后的几点

1. 父类private成员在中无子类论以什么方式继承都是不可见的。这里的不可见是指父类的私有成员还是被继承到了子类对象中,但是语法上限制子类对象不管在类里面还是类外面都不能去访问它


2. 父类private成员在子类中是不能被访问,如果父类成员不想在类外直接被访问,但需要在子类中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的


3. 实际上面的表格我们进行一下总结会发现,父类的私有成员在子类都是不可见。父类的其他成员在子类的访问方式 == Min(成员在父类的访问限定符,继承方式),public > protected > private


4. 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式


5. 在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承,也不提倡
使用protetced/private继承,因为protetced/private继承下来的成员都只能在子类的类里
面使用,实际中扩展维护性不强

父子类之间的赋值转换

父子类之间可以进行赋值转换吗?答案是可以的,但是子类可以转换成父类,而父类无法转换成子类,看下面一段代码

注意这里是赋值转换,而不是类型转换,类型转换要创建一个临时变量,是类似于把int转换成double类型的这种,先创建一个double类型的临时变量,然后把int提升成double类型

但是赋值转换就是直接的转换,没有中间创建临时变量这个过程,是类似于int转换成int这种同类型之间的转换,如下图

因为子类继承了父类的成员,父类的成员子类都有,所以子类可以赋值转换成父类成员,但是父类成员中没有子类成员中特有的成员,因此不支持父类赋值给子类。这种类型转换就像把子类中父类的那一部分切掉赋值给父类,我们也称其为切片赋值

子类对成员变量的使用

目前为止,我们只是在口头上说子类继承了父类除private区域外的所有成员变量和方法,怎么证明呢?并且子类继承了之后又该如何使用呢?

调试程序,打开监视,我们可以看到test_b这个类的成员里,确实包含了plant类的成员,那该如何使用呢,如果父类的成员和子类的成员之间不同名,那么可以直接使用父类的成员类型,如下图,子类和父类中没有重名成员,可以直接使用

如下图,子类中存在成员变量string  _name和父类中的成员变量string  _name同名。那么此时,父类成员和子类成员之间就会构成隐藏关系或者叫重定义,父类的成员会被隐藏掉,此时直接访问_name,你会发现使用的是子类的_name,但是这不意味着父类的_name就没法使用了,要想使用父类的成员,需要加上访问符 父类 :: ,这种形式,显示访问

若想使用父类的_name,就要加上父类的访问符,具体操作如下 

子类对成员方法的使用 

对成员方法的使用和成员变量的使用是一样的,如果父类的成员方法名和子类的成员方法名不一样,那么就可以直接调用父类的成员方法,但是如果父类的成员方法名和子类的成员方法名重名了,那么父类的成员方法就会被隐藏,若想使用,就要加上父类的访问符

这里大家容易困惑的地方是,父类和子类的方法即使重名了,如果方法的参数不一样,那么为什么不构成重载关系,而还是构成隐藏关系呢?

这是因为重载关系要求两个方法在同一作用域下,父类的方法在父类的作用域中,而子类的方法在子类的作用域中

两个方法虽然重名,但是不在同一作用域下,故而无法构成重载关系。想一想,我们平时写的构成重载关系的函数是不是都在全局作用域下呢,在类中构成重载关系的方法,是不是处在同一个类的作用域下呢

 

子类的构造和析构函数

构造 

怎么使用父类的成员变量和方法已经知道了,还有问题没有解决,那就是子类和父类的构造函数,我们在继承父类时,一般是要对父类中的成员进行初始化的

这个也很简单嘛,我们既然都知道了如何使用父类的成员,那么在子类的构造函数中给父类的成员进行赋值,不就可以完成初始化操作了嘛

这个操作是有问题的,首先子类无法在初始化列表中直接使用父类的成员,因为父类此时还没有创建,其次,你在子类构造函数的函数体里引用父类的成员,说明此时父类已经创建并完成初始化了,这属于赋值操作而不是初始化

那么如何在创建父类时就对父类进行初始化呢?首先要看父类有没有默认构造,如果父类有默认构造,我们可以选择让父类创建时自动调用默认构造

如果父类没有默认构造,那么我们就需要显示的去调用父类的构造函数,如下图父类有默认构造函数,子类在构造时可以选择依靠父类自己的默认构造

同样的的可以选择去显示的调用父类的构造,如果父类没有默认构造,那么只能选择显示的调用父类的构造 

拷贝构造 

接下来是拷贝构造,拷贝构造我们要关心如何给子类继承下来的父类进行拷贝,同时给子类自己独有的成员进行拷贝。后者很简单,关键是我们如何调用父类自己的拷贝构造,子类拷贝构造函数传过来的参数是一个子类的对象,我们要想调用父类的拷贝构造,是不是得给父类的拷贝构造传一个父类的对象,问题是我们此时没有父类的对象

如下图(为了看的更清晰,笔者这里删掉了父子类中的几个成员)

 

如何解决这个问题呢?想一想我们前面提到过的父子类之间的赋值转换,我们可以直接把子类的对象传给父类的拷贝构造,这个过程会进行切片式的赋值转换,就像同类型之间的转换一样,原来我们前面学到的赋值转换,在这里起到了大作用 

赋值重载

理解了拷贝构造,赋值重载并没有什么难度,就是需要注意两个点

1.赋值重载前要用this指针检查一下是否存在自己给自己赋值的情况

2.子类在调用父类的赋值重载时,一定要注意加上父类的访问符plant::,因为子类的赋值重载和父类的赋值重载函数名都是operator=,这就触发了隐藏关系,如果不加上父类访问符plant:: ,默认使用的是子类的operator=,从而导致无限调用子类operator=造成栈溢出

析构

析构的调用也会有很多的坑,接下来通过一个demo来简单的演示一下,析构调用会产生哪些问题,如下面的代码 

class plant {
public:
	plant(){}
	~plant() 
	{
		cout << "父类析构调用完成" << endl;
	}
};

class peashooter :public plant {
public:
	peashooter(){}	
	~peashooter()
	{
		plant::~plant();
		cout << "子类析构调用完成" << endl;
	}
};


int main()
{
	peashooter tmp;
	return 0;
}

子类析构时先调用父类的析构,然后自己再析构,看着没任何问题,再不能更正常了,然而

这是什么情况?为什么父类的析构会被调用两次,这是因为子类在调用析构函数的时候,会默认调用父类的析构函数,不需要我们手动去调用父类的析构,正因为我们显示调用了一次父类的析构,从而造成父类两次析构的现象,可以给父类new一个数组,来检测是否会造成父类重复析构的情况,代码如下

class plant {
public:
	int* _price = new int[10];
	plant(){}
	~plant() 
	{
		delete[] _price;
		cout << "父类析构调用完成" << endl;
	}
};

class peashooter :public plant {
public:
	peashooter(){}
	int* _hurt_value = new int[10];	
	~peashooter()
	{
		plant::~plant();
		delete[] _hurt_value;
		cout << "子类析构调用完成" << endl;
	}
};

int main()
{
	peashooter tmp;
	return 0;
}

 

 继承与友元

继承与友元是比较好理解的,其实就一句话,父类的有元关系不会被继承

如下面的测试demo

继承与类的静态成员 

继承与类的静态成员也是比较轻松的一个知识点,同样一句话也可以总结,静态成员属于整个类,所有对象,同时也属于所有子类的对象

这里需要注意的是,静态成员是存放在静态区的,也就是说,我们不需要创建一个对象才能访问静态成员,如下面的代码演示

tmp指针变量并没有指向一个具体的对象,而直接可以去静态区找静态变量_price

同样(*tmp)._price,并不是在解引用tmp,而是去静态区找变量_price

多继承&菱形继承

多继承是C++中比较麻烦的一部分,这倒不是多继承的问题,而是在使用多继承时会碰到菱形继承这种比较麻烦的多继承形式,属于解决了一个问题又引入了另一个问题这种不断挖坑填坑的行为

多继承意如其名,指一个类继承了两个及以上的类,如下面代码中类C就是多继承,同时继承了类A和类B

class A {
public:
	int _a;
};

class B {
public:
	int _b;
};

class C : public A, public B{
public:
	int _c;
};

如果到这就结束,那么多继承也没有什么可怕的地方,反而还挺好用,一个类拥有了其它的类的属性,多好啊。但是生活没有如果,菱形继承它就来了,下面的代码就是一个简单的菱形继承的模型

class A {
public:
	int _a;
};

class B: public A {
public:
	int _b;
};

class C: public A{
public:
	int _c;
};

class D: public B, public C {
public:
	int _d;
};

 为什么叫菱形继承,画个图就明白了

 

那又可怕在哪里呢? 看下图

 

1. 首先是二义性,当类D想访问变量_a时,它没有办法直接访问,因为_a变量有两个,它继承了类B,类B中有一个_a, 它也继承了类C,类C中有一个_a, 编译器如何知道你访问的是哪一个_a呢?编译器只能报错二义性

2. 数据冗余,类B和类C中同时都含有_a,可是我只需要一个呀,现在你还只是一个_a,如果是一个非常大的string串,非常大的vector,我只需要一份,而我们继承了两份,可想而知这是多大的空间浪费呀

二义性的问题我们费点力还是可以解决的,因为只要加上访问符就知道访问的是哪一个了?例如想访问类B中的_a,只需要加上 B::_a,这样勉强可以解决

但是冗余的问题怎么办,咱C/C++人的使命就是榨干CPU,榨干内存,如此大的冗余,根本无法忍受,这谁能受得了,感觉亏了一个亿,别说用了,光想想就气不打一处来

为了解决这个问题,C++委员会提出了虚继承的概念

虚继承的提出就是为了解决菱形继承的冗余问题,作用听起来容易,但是解释起来还是有一些小麻烦的,接下来就开始刨析何为虚继承

咱们看看没有使用虚继承之前的内存分布图是怎样的

通过内存分布以及监视图,我们可以发现,类D中确实包含了类B和类C,且类B和类C中都包含了类A,也就是存在了两个_a 

接下来我们开始使用虚继承,虚继承要求在继承的时候加上关键字virtual,并不是D在继承类B和类C时加上virtual,而是类B和类C在继承类A时加上virtual,如下述代码

class A {
public:
	int _a;
};

class B: virtual public A {
public:
	int _b;
};

class C: virtual public A{
public:
	int _c;
};

class D: public B, public C {
public:
	int _d;
};

int main()
{
	D test;
	test.B::_a = 1;
	test._b = 2;
	test.C::_a = 3;
	test._c = 4;
	test._d = 5;
	return 0;
}

使用虚继承之后,我们再去看看类D的内存分布有没有什么变化(这个时候我们就不要再查看监视窗口了,监视窗口做了一些优化,容易造成误解)

什么情况,加上virtual之后,原本类B和类C中存放_a的位置存放成其它的东西了,转而代之像是存放了一个地址,而且_d = 5之后,后面又出现了一个值为3的东西,我记得类C最后给_a赋值为3,这个难道就是_a吗?那它为什么又跑到最后面了

咱们一个一个探索,先看看,类B中存放的40 9b 3a 00 以及类C中存放的 48 9b 3a 00这两个地址到底指向了哪里 

可以发现,这两个地址分别指向了两个值,一个值是14,另一个值是0c

这很像是偏移量,让类B的起始地址加上14看看指向哪里?0x0020f96c加上20,指向的是0x0020f980,这不就是刚才我们打问号的那个地址吗?再让类C的起始地址加上0c看看指向哪里? 0x0020f947加上0c,指向的是ox0020f980,竟然也是打问号的这个地址,并且打问号的那个地址的值是3,正是类C对其进行的赋值

如此以来,我们便明白了,加上虚继承之后,类B和类C继承自类A的 _a 确实被合并成一个元素了, 并且把这个_a 放到了类D的末尾,但是为了让类B和类C都能够找到_a,于是把_a的相对于类B类C起始地址的偏移量放到类B,类C中

可能你会说,这并没有节省内存呀,反而多花销了内存,这是因为此时的_a太小了,造成花销比节省大几个字节,如果换成一个占比内存很大的数据类型,那就能体现出其作用了

继承与组合 

继承是子类直接继承父类中可继承的元素,组合就是一个类包含另一个类

举个例子

 

在两个都能满足我们的需求时,推荐使用组合,而不是继承,一方面继承是继承一个类中除private区域所有的东西,会导致耦合性很高,假设父类protected区域某个地方出问题,麻烦也会继承给子类,要考虑更多的细节

而组合是类B包含类A,即使类A中某些protected区域出现问题,但是不妨碍我类B使用,因为类B只能使用类A的public区域,耦合性很低

如上便是继承的全部讲解,继承也是一把双刃剑,用好了能大大节省开发成本,但是用不好,会给自己带来很多麻烦,所以大家可以用,但是要慎用 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1056286.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

zkLogin构建者的最佳实践和业务思考

随着zkLogin在Sui主网上线&#xff0c;构建者可以开始为其应用程序提供丝滑的帐户创建服务。与任何新技术集成一样&#xff0c;构建者需要考虑许多重要的问题&#xff0c;以降低风险并成功优化。 本文概述了其中一些考虑因素&#xff0c;并突出了zkLogin文档中提供的明确指导。…

Linux-centos系统安装MySql5.7

1.配置yum仓库 1.1配置yum仓库 rpm --import https://repo.mysql.com/RPM-GPG-KEY-mysql-2022 1.2 安装Mysql yum库 rpm -Uvh http://repo.mysql.com//mysql57-community-release-el7-7.noarch.rpm 2.使用yum安装Msql 说明&#xff1a;下载大约5分钟左右 yum -y install mysq…

基于Java的教学评价管理系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言系统功能结构图系统ER图具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划…

分页前后端完整代码

一、后端代码 User实体类要继承PageVo package com.like.entity;import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import com.like.common.PageVo; import…

web:[SUCTF 2019]EasySQL

题目 点进页面&#xff0c;页面显示为 没有其他的提示信息&#xff0c;查看源代码看看是否有提示 什么都没有&#xff0c;由题目名为easysql&#xff0c;能想到这道题与sql注入有关 先输入1查看回显 再输入其他数字或字母看看回显 都是无显示 先验证是否存在sql注入 构造pay…

python爬取csdn个人首页中的所有封面

#爬取csdn个人首页中的所有封面 import requests import json import reurlhttps://blog.csdn.net/community/home-api/v1/get-business-list? headers{User-Agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safar…

Scala第十二章节

Scala第十二章节 1.读取数据 2.写入数据 3.案例&#xff1a;学生成绩表 scala总目录 文档资料下载

Origin分段显示柱状图

注意这里生成的是柱状图&#xff0c;而不是直方图。因此用到的是plot -> column/bar/pie -> stacked column&#xff1a; 而不是Statistical->histogram。 先上最终的作图效果&#xff1a; 单个柱的柱状图 第一步先填充数据&#xff0c;如图左所示&#xff0c;然后选…

P1540 [NOIP2010 提高组] 机器翻译(模拟)

[NOIP2010 提高组] 机器翻译 题目背景 小晨的电脑上安装了一个机器翻译软件&#xff0c;他经常用这个软件来翻译英语文章。 题目描述 这个翻译软件的原理很简单&#xff0c;它只是从头到尾&#xff0c;依次将每个英文单词用对应的中文含义来替换。对于每个英文单词&#xf…

Java类与对象

文章目录 引出概念快速入门对象内存布局属性概念创建对象访问属性对象分配机制成员方法&#x1f996;注意事项和细节 引出 ●看一个养猫猫问题 张老太养了两只猫猫: 一只名字叫小白, 今年3岁, 白色. 还有一只叫小花, 今年100岁, 花色. 请编写一个程序, 当用户输入小猫的名字时…

Qt自定义菜单

Qt开发过程中&#xff0c;弹出菜单时我们一般使用QMenu,但是QMenu都是一条项固定的格式&#xff0c;如查想要自己的设计界面就没法使用默认的Action项了&#xff0c;因此我们得用自定义的QMenu。 本篇介绍使用自定义的QMenu设计出UI。我们使用QWidget QWidgetAction来实现。Q…

代码随想录 Day8 栈(FILO)与队列(FIFO) LeetCode T232 用栈实现队列 LeetCodeT225 用队列实现栈

题目详细思路来自于:代码随想录 (programmercarl.com) 栈和队列都是大家不陌生的数据结构,我们之前的栈和队列一般是用数组或链表来实现的 , 这里我们给出实现方式,用于帮助更好的理解. 1.用链表实现栈 /* 基于链表实现的栈 */ class LinkedListStack { private ListNode sta…

记录我的网站的上线的全过程

我的网站开发全过程记录: 提示: 大部分是cv代码,少部分样式进行了修改和借鉴,主要花费时间在部署上面,出现的问题 开始: 三个博主: 在掘金上认识了搜狐前端-- 英杰 , 掘金链接:yingjieweb 的个人主页 - 动态 - 掘金 偶然间看见了英杰的网站,感觉非常不错,产生自己弄的想法,…

mybatis-spring集成数据库连接池开启注解式开发

目录 1. 引入依赖包 2. 集成配置文件 2.1 开启注解式开发 2.2 spring引入外部配置文件 2.3 数据库连接池 2.4 spring整合mybatis 2.5 自动代理 3. 注解式开发的几个常用注解 4. spring-test 附录一&#xff1a;spring常用注解 1. 引入依赖包 <!--spring整合mybat…

【面试经典150 | 矩阵】旋转图像

文章目录 写在前面Tag题目来源题目解读解题思路方法一&#xff1a;原地旋转方法二&#xff1a;翻转代替旋转 写在最后 写在前面 本专栏专注于分析与讲解【面试经典150】算法&#xff0c;两到三天更新一篇文章&#xff0c;欢迎催更…… 专栏内容以分析题目为主&#xff0c;并附带…

Mac安装Ecplise产品报错:dose not contain the JNI_CreateJavaVM symbol

1. 絮絮叨叨 工作中需要借助Ecplise Memory Analyzer (MAT)分析dump文件&#xff0c;直接下载、安装、运行MAT报错 询问同事后&#xff0c;同事说可以先安装Ecplise&#xff0c;再以插件的形式安装MAT下载、安装好Eclipse&#xff0c;点击运行仍然报错&#xff0c;且错误信息一…

区块链3.0时代 基于GoMars构建的新概念TravelFi能否注入新力量?

区块链技术进入3.0时代 后疫情时代&#xff0c;全球数字化进程不断加快&#xff0c;世界范围内的移动通信、互联网技术及各类数字化应用的社会普及率也正在快速提升&#xff0c;疫情推动了互联网经济的增长&#xff0c;也让数字经济的价值开始显现。 数字经济一词的由来至今已经…

2023最新注册小程序以及云开发环境的创建

前言&#xff1a; 我们前面虽然可以用测试号创建小程序,但是测试号有很多功能会受限,比如我们接下来要讲的云开发,必须是注册小程序后才可以使用, 一&#xff0c;注册小程序 官方注册文档&#xff1a; 产品定位及功能介绍 | 微信开放文档 (qq.com)https://developers.weixin.…

servlet 线程模型 异步

在 servlet 3.0 之前&#xff0c;请求与线程的对应关系是1:1&#xff0c;对应jvm与操作系统的线程的关系。 https://jcp.org/en/jsr/detail?id315 https://jcp.org/en/jsr/detail?id340 从 servlet 3.0 开始&#xff0c;开始有了异步相关功能 容器线程池与业务线程池开始单独…

第九章 动态规划 part13 300. 最长递增子序列 674. 最长连续递增序列 718. 最长重复子数组

第五十二天| 第九章 动态规划 part13 300. 最长递增子序列 674. 最长连续递增序列 718. 最长重复子数组 一、300. 最长递增子序列 题目链接&#xff1a;https://leetcode.cn/problems/longest-increasing-subsequence/ 题目介绍&#xff1a; 给你一个整数数组 nums &#xff…