[C++基础]-继承

news2024/11/20 6:32:10

前言

作者小蜗牛向前冲

名言我可以接受失败,但我不能接受放弃

  如果觉的博主的文章还不错的话,还请点赞,收藏,关注👀支持博主。如果发现有问题的地方欢迎❀大家在评论区指正。

目录

一、模板的知识补充

1、非类型模板参数

2、模板的特化

2.1基本概念:

2.2函数模板的特化:

 2.3类模板的特化

3、模板的分离编译 

二、继承 

1、继承的概念

2、基类和派生类对象赋值转换

3、继承中的作用域

4、派生类的默认成员函数

5、继承的语法小知识 

6 、复杂的菱形继承及菱形虚拟继承

7、继承和组合


本期学习目标:了解模板的非类型模参数和特化,认识什么是继承。

一、模板的知识补充

在前面的博客中我们学习了模板的大部分语法,有需要的同学可以查找我一起写的博客。

下面我们要进一步的了解模板,什么是类型模板参数?什么是类的特化?什么是模板的分离编译?

1、非类型模板参数

对于模板参数我们可以分为类型参数于非类型参数:

类型形参即:出现在模板参数列表中,跟在class或者typename之类的参数类型名称

非类型形参:就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用。

 举例:

	//类型模板参数
	template<class T1,class T2>
	class studnt
	{
	private:
		T1 _name;
		T1 _sex;
		T2 _age;
	};
	//非类型参数模板
	template<class T, size_t N>
	class array
	{
	private:
		T _a[N];
	};
}

注意: 

1. 浮点数、类对象以及字符串是不允许作为非类型模板参数的。

2. 非类型的模板参数必须在编译期就能确认结果。

2、模板的特化

2.1基本概念:

 在一般情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结 果,需要特殊处理,比如:实现了一个专门用来进行小于比较的函数模板:

// 函数模板 -- 参数匹配
template<class T>
bool Less(T left, T right)
{
 return left < right;
}
int main()
{
 cout << Less(1, 2) << endl; // 可以比较,结果正确
 Date d1(2022, 7, 7);
 Date d2(2022, 7, 8);
 cout << Less(d1, d2) << endl; // 可以比较,结果正确
 Date* p1 = &d1;
 Date* p2 = &d2;
 cout << Less(p1, p2) << endl; // 可以比较,结果错误
 return 0;
}

在上面代码中,我们得出比较二个整数是可以的,比较日期类的大小也是没问题的,但是当我们分别取出d1和d2的地址让Less去比较日期类的时候,这时候我们就得不到我们想要的结果了,因为,这个时候模板推演的是指针,Less函数是按照地址的大小进行比较的,而没有按照日期类的大小,所以在这种场景下,我们就要考虑到进行模板的特化。

在原模板类的基础上,针对特殊类型所进行特殊化的实现方式。模板特化中分为函数模板特化与类模板特化。

2.2函数模板的特化:

1. 必须要先有一个基础的函数模板

2. 关键字template后面接一对空的尖括号<>

3. 函数名后跟一对尖括号,尖括号中指定需要特化的类型

4. 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误

	//模板特化
	// 函数模板 -- 参数匹配
	template<class T>
	bool Less(T left, T right)
	{
		return left < right;
	}
	// 对Less函数模板进行特化
	template<>
	bool Less<Date*>(Date* left, Date* right)
	{
		return *left < *right;
	}

 这里我们也可以不用模板就能解决上面的问题,我们在针对上面的功能在写一个函数重载就可以了。

bool Less(Date* left, Date* right)
{
 return *left < *right;
}

这样写就显的代码非常明了,可读性非常高,因为对于一些参数类型的函数模板,特化就显示的非常复杂,所以函数模板不建议使用特化

 2.3类模板的特化

全特化

全特化即是将模板参数列表中所有的参数都确定化

举例:

	template<class T1,class T2>
	class teacher
	{
	public:
		teacher()
		{
			cout << "teacher 这是一个测试" << endl;
		}
	private:
		T1 _name;
		T2 _age;
	};

	//全特化
	template<>
	class teacher<char ,int>
	{
	public:
		teacher()
		{
			cout << "teacher :char int" << endl;
		}
	private:
		char _name;
		int _age;
	};

 偏特化

任何针对模版参数进一步进行条件限制设计的特化版本。比如我们对上面您个模板类:

部分特化:

	//部分特化,第二个参数
	template<class T>
	class teacher<T, int>
	{
	public:
		teacher()
		{
			cout << "teacher: T int" << endl;
		}
	private:
		T _name;
		int _age;
	};

参数更进一步的限制:

偏特化并不仅仅是指特化部分参数,而是针对模板参数更进一步的条件限制所设计出来的一个特化版本

	//限制性特化
	template<class T1, class T2>
	class teacher<T1*, T2*>
	{
	public:
		teacher()
		{
			cout << "teacher: T1* T2*" << endl;
		}
	private:
		T1 _name;
		T2 _age;
	};

测试:

int main()
{
    teacher<char, int> man1;// 调用全特化版本
	teacher<string , int> man2;// 调用特化的int版本
    teacher<char* ,int*> man3;//特化调用指针版本
	return 0;
}

3、模板的分离编译 

什么是分离编译:

一个程序(项目)由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件链接起来形成单一的可执行文件的过程称为分离编译模式。

模板的分离编译: 

这里其实是三个文件分别在a.h,a.cpp,main。cpp
// a.h
template<class T>
T Add(const T& left, const T& right);
// a.cpp
template<class T>
T Add(const T& left, const T& right)
{
 return left + right;
}
// main.cpp
#include"a.h"
int main()
{
 Add(1, 2);
 Add(1.0, 2.0);
 
 return 0;
}

就是将模板在.h的文件声明,在.cpp中定义。但是我们这样做编译器会报错。

重定义问题:

这是因为:在C++中,函数模板的定义通常都放在头文件中,而头文件可能被多个源文件包含,当多个源文件包含相同的头文件时,其中的函数模板定义也会被多次包含,从而引发重定义问题。

编译器会对头文件add.h进行两次编译,并生成两个不同的目标文件。然后,编译器试图将两个目标文件链接到一起时,就会发现它们之间存在重复定义的符号,从而导致连接错误。重复定义的符号指的是在多个目标文件中都存在,名称相同但实体不同的符号。在C++中,符号通常是函数名,变量名,类名

下面是模板分离编译遇到到链接问题: 

解决方法: 

1、将函数模板的定义放到头文件中,但这样也存在不足的地方:

  • 将函数定义写在头文件中,暴露了函数的实现细节
  • 不符合分离编译模式的规则

2、 模板定义的位置显式实例化

// 在使用模板的源文件中显式实例化模板
template int Add(const int& x,const int& y);
template double Add(const double& x,const double& y);

但是这种方式一般不推荐

  1. 增加了代码量

使用显式实例化需要在每个需要使用的源文件中都显式地提供实例化类型,这会增加代码量并降低可维护性。

  1. 容易出现“遗漏”错误

如果某个源文件未显式地提供所需的实例化类型,则该文件中使用的模板将无法正确地实例化,从而导致链接错误。

  1. 可移植性差

由于不同的编译器和操作系统可能对显式实例化的支持程度不同,因此代码的可移植性可能会受到影响。

 相比之下,将模板的声明和定义都放在头文件中,并使用 inline 关键字修饰模板的定义,可以有效地解决模板分离编译问题。这种方法不需要显式实例化,并且可以确保每个使用模板的源文件都能看到模板的定义。

模板总结:

【优点】

  1. 模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库STL因此而产生。
  2. 增强了代码的灵活性。

【缺陷】

  1. 模板会导致代码膨胀问题,也会导致编译时间变长。
  2. 出现模板编译错误时,错误信息非常凌乱,不易定位

二、继承 

1、继承的概念

继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保
持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类。继承呈现了面向对象
程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,继
承是类设计层次的复用。

下面的讨论都是基本此类展开:

//继承
class Person
{
public:
	void Print1()
	{
		cout << "name:" << _name << endl;
		cout << "age:" << _age << endl;
		cout << "sex:" << _sex << endl;
	}
	string _sex = "man";
protected:
	string _name = "pjb";//姓名
	int _age = 18;//年龄
};

class Student :public Person
{
public:
	void Print2()
	{
		_name = "张三";
		_age = 20;
		_sex = "man";
		_grand = 99;
		cout << "name:" << _name << endl;
		cout << "age:" <<_age<< endl;
		cout << "sex:" <<_sex << endl;
		cout << "_grand :" << _grand<< endl;
	}
protected:
	int _grand = 100;//分数

};
class Teacher :public Person
{
	void Print3()
	{
		cout << "name:" << _name << endl;
		cout << "age:" << _age << endl;
		cout << "sex:" << _sex << endl;
		cout << "_number :" << _number << endl;
	}
protected:
	int _number = 10086;//号码
};
int main()
{
	Student s;
	Teacher t;
	s.Print();
	t.Print();
	return 0;
}

 这里我们可以看到类Teatcher和Student类都复用了类Person的成员变量

继承的定义:

下面我们看到Person是父类,也称作基类。Student是子类,也称作派生类

继承关系和访问限定符:

继承基类成员访问方式的变化

类成员/继承方式public继承protected继承private继承
基类的public成员派生类的public成员派生类的protected
成员
派生类的private
成员
基类的protected
成员
派生类的protected
成员
派生类的protected
成员
派生类的private
成员
基类的private成
在派生类中不可见在派生类中不可见在派生类中不可见

总结:

1. 基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它。
2. 基类private成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的。
3. 实际上面的表格我们进行一下总结会发现,基类的私有成员在子类都是不可见。基类的其他成员在子类的访问方式 == Min(成员在基类的访问限定符,继承方式)public > protected> private
4. 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式。
5. 在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承,也不提倡
使用protetced/private继承因为protetced/private继承下来的成员都只能在派生类的类里
面使用,实际中扩展维护性不强。

2、基类和派生类对象赋值转换

  • 派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用,这里有个形象的说法叫切片或者切割寓,意把派生类中父类那部分切来赋值过去。
  • 基类对象不能赋值给派生类对象。
  • 基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用,但是必须是基类的指针是指向派生类对象时才是安全的,这里基类如果是多态类型,可以使用RTTI(RunTime Type Information)的dynamic_cast 来进行识别后进行安全转换。

简单的理解基类和派生对象的转换,其实就是指:子类可以被赋值给基类 ,而父类不能被赋值给子类,因为子类中有的成员而父类不一定有。这里要特别注意的是子类在给父类赋值不存在类型转换。

3、继承中的作用域

1. 在继承体系中基类和派生类都有独立的作用域
2. 子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义。(在子类成员函数中,可以使用 基类::基类成员 显示访问)
3. 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。
4. 注意在实际中在继承体系里面最好不要定义同名的成员

为了更好的理解继承中的作用域,来看下面代码:

下面二个fun函数什么关系

// 父类和子类的同名成员函数,函数名相同就构成隐藏
class A
{
public:
	void fun()
	{
		cout << "A::func()" << endl;
	}
};
class B : public A
{
public:
	void fun(int i)
	{
		cout << "B::func(int i)->" << i << endl;
	}
};

上面我在不特别提醒的情况下,有些同学可能会认为,二个函名相同,但是参数不同,这不就构成了重载吗?其实不不然,因为函数重载在的前提二个函数在同一作用域中,上面我们说了子类和父类都是有自己的作用域的,所以这里应该构成的是隐藏(重定义)。

4、派生类的默认成员函数

6个默认成员函数,“默认”的意思就是指我们不写,编译器会变我们自动生成一个,那么在派生类中,这几个成员函数是如何生成的呢?
1. 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用
2. 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化
3. 派生类的operator=必须要调用基类的operator=完成基类的复制
4. 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序。
5. 派生类对象初始化先调用基类构造再调派生类构造
6. 派生类对象析构清理先调用派生类析构再调基类的析构。
7. 因为后续一些场景析构函数需要构成重写,重写的条件之一是函数名相同。那么编译器会对析构函数名进行特殊处理,处理成destrutor(),所以父类析构函数不加virtual的情况下,子类析构函数和父类析构函数构成隐藏

下面对继成派生类的默认成员函数的讨论都基于此类:

class Person
{
public:
	Person(const char* name)
		: _name(name)
	{
		cout << "Person()" << endl;
	}

	Person(const Person& p)
		: _name(p._name)
	{
		cout << "Person(const Person& p)" << endl;
	}

	Person& operator=(const Person& p)
	{
		cout << "Person operator=(const Person& p)" << endl;
		if (this != &p)
			_name = p._name;

		return *this;
	}

	~Person()
	{
		cout << "~Person()" << endl;
	}
protected:
	string _name; // 姓名
};

// 派生类中
// 1、构造函数,父类成员调用父类的构造函数完成初始化
class Student : public Person
{
public:
	Student(const char*name, int num)
		:Person(name)
		, _num(num)
	{}

	Student(const Student& s)
		:Person(s)
		, _num(s._num)
	{}

	Student& operator=(const Student& s)
	{
		if (this != &s)
		{
			Person::operator=(s);
			_num = s._num;
		}

		return *this;
	}

protected:
	int _num; //学号
};

 根据上面代码我们简单验证一下,上面的结论:

我们在建立对象s1需要调用默认构造,我们发现这里子类是先调用父类的默认构造,在去掉用子类的默认构造的。

我们调试来看是先调用了父类的默认构造初始了_name,在去调用子类的默认构造初始化_num

我们在调用拷贝构造s2(s1),这时会直接调用父类的拷贝构造 。

在调用赋值重载时s1=s3,也会直接调用父类的赋值重载。

 在最后调用析构函数,是先析构子类的成员,在析构父类的成员。

注意:

1、子类析构函数和父类析构函数构成的关系是隐藏。

2、子类先析构,父类在析构。子类析构函数不需要显示调用父类的析构函数。自己会调用。

5、继承的语法小知识 

1、友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员

2、基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一个static成员实例
 

6 、复杂的菱形继承及菱形虚拟继承

单继承:一个子类只有一个直接父类时称这个继承关系为单继
 

 

多承:一个子类有两个或以上直接父类时称这个继承关系为多继

菱形继承:菱形继承是多继承的一种特殊情况
 

这里我们重点讨论多继承中的菱形继承,从下面的对象成员模型构造,可以看出菱形继承有数据冗余和二义性的问题。在Assistant的对象中Person成员会有两份。

 为了更好的理解菱形继承二异性的问题,下面我们简化一下代码,重新构建一个菱形继承

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;
};

 

通过内存窗口观察对象成员的模型,菱形继承的内存情况:

 可以看到,D 对象中存在着三部分成员 – 从 B 继承来的成员、从 C 继承来的成员以及 D 自身的成员;同时,由于 B 和 C 同时继承自 A,所以 D 对象中存在两份 A 的成员,从而造成数据冗余和二义性。

虚拟继承

为了解决菱形继承数据冗余和二义性的问题,C++11引入了虚拟继承 – 虚拟继承可以解决菱形继承的二义性和数据冗余的问题,如上面的继承关系,在 Student 和 Teacher 继承 Person 时使用虚拟继承,即可解决问题。需要注意的是,虚拟继承不要在其他地方去使用。

特别注意:虚拟继承是使用在产生菱形继承的地方,即菱形的腰部,而不是使用在最后出现问题的地方,即菱形的尾部

那我们知道了什么是虚拟继承,那我们进行来用内存窗口来观察,进行虚拟继承后,在内存上有什么变化

这里我们发现内存变的非常奇怪,原来存放B和C对象继承存变成了指针,由于d对象继承的a却放置在最下面,而且我们发现该窗口第二个整形的值恰好为 B/C 对象的起始地址与 A 对象的起始地址的偏移量。 (0x14 = 20B, 0x0c = 12B)

其中B 和 C 对象中多出来的那个指针指向的内容被称为虚基表。

那么为什么进行虚继承的类对象中要记录距离虚基类的偏移量呢?其实是为了满足切片的场景:

可以观察大盘,虽然 1 和 2 都是在 B 对象中去访问 A 成员变量 a,但是 A 的在内存中的位置是不同的,如果此时我们仍然到最下面去访问 _a,那么 2 访问的结果就会是 C 的虚基表指针;但是按照偏移量访问就则不会出现这种问题。

大家可能会认为,这样怎么会解决二义性的问题,我们这里不是占用了更多的空间吗?

其实不然,这是因为我们定义的类非常小,如果类占100个字节,我们存放一个虚基表只要4个字节,这里的节省空间是非常大的。

7、继承和组合

public继承是一种is-a的关系。也就是说每个派生类对象都是一个基类对象。
组合是一种has-a的关系。假设B组合了A,每个B对象中都有一个A对象。
优先使用对象组合,而不是类继承 。

为什么这么说?

  • 继承允许你根据基类的实现来定义派生类的实现。这种通过生成派生类的复用通常被称为白箱复用(white-box reuse)。术语“白箱”是相对可视性而言:在继承方式中,基类的内部细节对子类可见 。继承一定程度破坏了基类的封装,基类的改变,对派生类有很大的影响。派生类和基类间的依赖关系很强,耦合度高
  • 对象组合是类继承之外的另一种复用选择。新的更复杂的功能可以通过组装或组合对象来获得。对象组合要求被组合的对象具有良好定义的接口。这种复用风格被称为黑箱复用(black-box reuse),因为对象的内部细节是不可见的。对象只以“黑箱”的形式出现。组合类之间没有很强的依赖关系,耦合度低。优先使用对象组合有助于你保持每个类被封装。
  • 际尽量多去用组合。组合的耦合度低,代码维护性好。不过继承也有用武之地的,有些关系就适合继承那就用继承,另外要实现多态,也必须要继承。类之间的关系可以用继承,可以用组合,就用组合

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

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

相关文章

2023-9-23 最大不相交区间数量

题目链接&#xff1a;最大不相交区间数量 #include <iostream> #include <algorithm>using namespace std;const int N 100010;int n;struct Range {int l, r;bool operator< (const Range &W) const {return r < W.r;} }range[N];int main() {cin >…

Unity中的两种ScriptingBackend

一&#xff1a;前言 二&#xff1a;两种模式的介绍 ios&#xff1a;unity只有il2cpp模式的编译才支持64位系统&#xff0c;mono是不支持的&#xff0c;在快速开发阶段仍然支持Mono&#xff0c;但是不能再向Apple提交Mono(32位)的应用 苹果在2016年1月就要求所有新上架游戏必须支…

ASO优化之竞争对手的选择取决于什么

任何具有良好ASO策略的应用程序都是来自于良好的基准&#xff0c;不仅可以对市场有总体概述&#xff0c;还可以发现应用的增长潜力以及如何在不同的应用商店中获得知名度。在发布应用之前&#xff0c;需要制定ASO策略以获得搜索和浏览可见性&#xff0c;所以研究竞争对手是基础…

【小沐学NLP】关联规则分析Apriori算法(Mlxtend库,Python)

文章目录 1、简介2、Mlxtend库2.1 安装2.2 功能2.2.1 User Guide2.2.2 User Guide - data2.2.3 User Guide - frequent_patterns 2.3 入门示例 3、Apriori算法3.1 基本概念3.2 apriori3.2.1 示例 1 -- 生成频繁项集3.2.2 示例 2 -- 选择和筛选结果3.2.3 示例 3 -- 使用稀疏表示…

Klocwork 2023.2 windows

Klocwork 2023.2 windows 2692407267qq.com&#xff0c;更多内容请见http://user.qzone.qq.com/2692407267/

es6新语法特性+vue2的学习笔记

1. es6 ECMA的第6版&#xff0c;15年发布&#xff0c;规定新的语法特性 2. let声明变量 varlet声明的变量会越狱声明的变量有严格的作用域可以声明多次只能声明一次会变量提升(未声明的变量不会报错)不会变量提升 代码案例&#xff1a; <script>// {// var a 1;/…

点分治维护dp+连通块上新型dp思路+乘积方面进行根号dp:0922T4

首先连通块&#xff0c;所以点分治肯定是 Trick1 钦定选根的连通块dp 对于钦定选根的连通块dp&#xff0c;有一种常见思路 先对原树求其dfn序&#xff0c;按dfn序倒序求解 具体的&#xff0c;对于当前点 i i i&#xff08;注意这里都是指dfn序&#xff09;&#xff0c;我们…

如何申请办理400电话?

导语&#xff1a;随着企业的发展和市场竞争的加剧&#xff0c;越来越多的企业开始意识到拥有一个400电话的重要性。本文将介绍如何申请办理400电话&#xff0c;帮助企业提升客户服务质量和品牌形象。 一、了解400电话的概念和优势 400电话是一种企业客服电话号码&#xff0c;…

描述符——设备描述符

文章目录 描述符定义描述符实现描述符含义 描述符定义 描述符实现 /*** brief Device descriptor.*/ typedef struct __attribute__ ((packed)) {uint8_t bLength ; /**< Size of this descriptor in bytes. */uint8_t bDescriptorType ; /**< DEVICE D…

Linux0.11——第三回 做好访问内存的最基础准备工作

前面两回是把启动区的代码复制来复制去的&#xff0c;这里我们要讨论的就是操作系统怎么为程序访问内存的方式做初步规划的&#xff1f; 操作系统的代码最开头的 512 字节的数据&#xff0c;先从硬盘的启动区移动到了内存 0x7c00 处&#xff0c;然后又立刻被移动到 0x90000 处…

mybatis-plus中更新null值的问题

文章目录 前言一、情景介绍二、方法分析三、原因分析四、解决方式五、方式扩展总结 前言 本文主要介绍 mybatis-plus 中常使用的 update 相关方法的区别&#xff0c;以及更新 null 的方法有哪些等。 至于为什么要写这篇文章&#xff0c;首先是在开发中确实有被坑过几次&#x…

Json文件序列化读取

Json文件 [{"name":"清华大学","location":"北京","grade":"1"},{"name":"北京大学","location":"北京","grade":"2"} ] 安装包 代码 Program.c…

睿趣科技:抖音开网店真的可以相信吗

随着社交媒体的快速发展&#xff0c;抖音已经成为了一个备受欢迎的平台&#xff0c;尤其是对于那些希望在电商领域有所作为的人们。许多人开始在抖音上开设网店&#xff0c;以获取额外的收入或建立自己的事业。然而&#xff0c;对于抖音开网店是否真的可以相信&#xff0c;存在…

java基础学习之变量与运算符

一&#xff0c;关键字 1&#xff0c;定义&#xff1a;被java语言赋予了特殊含义&#xff0c;用作专门用途的字符串或单词。 2&#xff0c;特点&#xff1a;关键字全都是小写字母。 3&#xff0c;关键字一共50个&#xff0c;其中const和goto是保留字。 4&#xff0c;true&#x…

硕士应聘大专老师

招聘信息 当地人社局、学校&#xff08;官方&#xff09; 公众号&#xff08;推荐&#xff09;&#xff1a; 辅导员招聘 厦门人才就业信息平台 高校人才网V 公告出完没多久就要考试面试&#xff0c;提前联系当地院校&#xff0c;问是否招人。 校招南方某些学校会直接去招老师。…

Web自动化测试测试常见BUG

【软件测试面试突击班】如何逼自己一周刷完软件测试八股文教程&#xff0c;刷完面试就稳了&#xff0c;你也可以当高薪软件测试工程师&#xff08;自动化测试&#xff09; 1.翻页 翻页时&#xff0c;没有加载数据为空&#xff0c;第二页数据没有请求 翻页时&#xff0c;重复请…

“淘宝” 开放平台接口设计思路(内附API接口免费接入地址)

最近对接的开放平台有点多&#xff0c;像淘宝、天猫、京东、拼多多、快手、抖音等电商平台的开放平台基本对接了个遍&#xff0c;什么是CRUD BODY也许就是这样的吧&#xff01;&#xff01;&#xff01; 经过这几天的整理&#xff0c;脑子里大概有了个开放平台接口的设计套路&…

mysql优化之索引

索引官方定义&#xff1a;索引是帮助mysql高效获取数据的数据结构。 索引的目的在于提高查询效率&#xff0c;可以类比字典。 可以简单理解为&#xff1a;排好序的快速查找数据结构 在数据之外&#xff0c;数据库系统还维护着满足特定查找算法的数据结构&#xff0c;这种数据…

面试题:ElasticSearch是什么?应用场景是什么?

文章目录 1、什么是搜索2、如果用数据库做搜索会怎么样3、什么是全文检索、倒排索引和Lucene4、ElasticSearch是什么ElasticSearch的功能ElasticSearch的应用场景ElasticSearch的特点 ElasticSearch是一个分布式&#xff0c;高性能、高可用、可伸缩的搜索和分析系统 看了上面这…

泛型编程<T extends Comparable<? super T>>是什么意思

今天看到了两个这样的写法,非常好奇。 <T extends Comparable<? super T>>public class BplusTree<K extends Comparable </K/>,V>下面是不同人对这种写法的解释 大概理解的意思是实现不同类之间属性的对比 转载链接 这段代码是什么意思呢 public…