C++好难(9):C++的多态

news2024/11/27 20:21:28

目录

1.多态的概念

2.多态的定义及实现:

🍉多态的构成条件

 🍉虚函数

🍉虚函数的重写

🍉虚函数重写的三个例外

🍒子类虚函数可以不加vector

🍒析构函数的重写

🍒协变(返回值可以不同)

🍉C++11的 override 和 final

🍒final:修饰虚函数,表示改虚函数不能被重写

🍒override: 检查派生类虚函数是否重写了基类某个虚函数

🍉重载、重写(覆盖)、隐藏(重定义)的概念对比

3.抽象类

🍉概念:

🍉接口继承和实现继承:

4.多态的原理

🍉虚函数表

🍉多态的原理

🍉动态绑定与静态绑定

5.单继承和多继承的虚函数表

🍉单继承中的虚函数表

🍉单继承中的虚函数表

🍉菱形继承、菱形虚拟继承

6.继承和多态常见的面试问题:

🍉概念考察

🍉问答题:


1.多态的概念

多态的概念:通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态

举个例子:比如买票这个行为,当普通人买票时,是全价买票;学生买票时,是半价买票;军人买票时是优先买票

2.多态的定义及实现:

🍉多态的构成条件

多态是在不同继承关系的类对象,去调用同一函数,产生了不同的行为。
比如 Student 继承了 Person Person 对象买票全价,Student 对象买票半价

那么再继承中要构成多态还有两个条件:

  • 1. 必须通过基类的指针或者引用调用虚函数
  • 2. 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写

 两个条件必须得满足,才能实现多态

 🍉虚函数

虚函数:就是被  virtual  修饰的类成员函数称为虚函数

class Student : public Person
{
public:
	virtual void BuyTicket() 
	{ 
		cout << "买票-半价" << endl;
	}
};

🍉虚函数的重写

虚函数的重写(覆盖):派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的返回值类型、函数名字、参数列表完全相同),称子类的虚函数重写了基类的虚函数。

1.虚函数的重写(三同):函数名、参数、返回值,这三个条件必须相同

2.基类的指针或者引用调用虚函数

class Person 
{
public:
	virtual void BuyTicket() 
	{
		cout << "买票-全价" << endl;
	}
};

class Student : public Person
{
public:
	virtual void BuyTicket() 
	{ 
		cout << "买票-半价" << endl;
	}
};

void Func(Person& p)
{
	p.BuyTicket();
}

int main()
{
	Person ps;
	Func(ps);

	Student st;
	Func(st);


	return 0;
}

正常使用多态,对比,去掉引用时的情况:

 

🍉虚函数重写的三个例外

🍒子类虚函数可以不加vector

在重写基类虚函数时,派生类的虚函数在不加virtual关键字时,虽然也可以构成重写
(因为继承后基类的虚函数被继承下来了在派生类依旧保持虚函数属性),
但是该种写法不是很规范,不建议这样使用

class Person 
{
public:
	virtual void BuyTicket() 
	{
		cout << "买票-全价" << endl;
	}
};
class Student : public Person
{
public:
	void BuyTicket() //不加victual也构成多态
	{ 
		cout << "买票-半价" << endl;
	}
};
void Func(Person& p)
{
	p.BuyTicket();
}

🍒析构函数的重写

如果基类的析构函数为虚函数此时派生类析构函数只要定义,无论是否加virtual关键字,都与基类的析构函数构成重写,

这是因为:虽然基类与派生类析构函数名字不同。看起来违背了重写的规则,其实不然,这里可以理解为编译器对析构函数的名称做了特殊处理编译后析构函数的名称统一处理成destructor

class Person 
{
public:
	virtual ~Person() { cout << "~Person()" << endl; }
};

class Student : public Person 
{
public:
	virtual ~Student() { cout << "~Student()" << endl; }
};

// 只有派生类Student的析构函数重写了Person的析构函数,
// 下面的delete对象调用析构函数,才能构成多态,
// 才能保证p1和p2指向的对象正确的调用析构函数。
int main()
{
	Person* p1 = new Person;
	Person* p2 = new Student;
	delete p1;
	delete p2;
	return 0;
}

  

如果不给析构函数加virtual,则不会调用派生类的析构函数,可能会导致内存泄漏

🍒协变(返回值可以不同)

基类与派生类虚函数返回值类型不同,必须是父子关系指针或引用(用到不多)

派生类重写基类虚函数时,与基类虚函数返回值类型不同。
即基类虚函数返回基类对象的指针或者引用,派生类虚函数返回派生类对象的指针或者引用时,称为协变。(了解)

class Person 
{
public:
	virtual Person* fun() { return this; }
};
class Student : public Person 
{
public:
	virtual Student* fun() { return this; }
};

//-----------上面或者下面都称为协变---------------//

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

class Person 
{
public:
	virtual A* f() { return new A; }
};
class Student : public Person 
{
public:
	virtual B* f() { return new B; }
};

 

🍉C++11的 override 和 final

从上面可以看出,C++对函数重写的要求比较严格,但是有些情况下由于疏忽,可能会导致函数 名字母次序写反而无法构成重载,而这种错误在编译期间是不会报出的,只有在程序运行时没有 得到预期结果才来debug会得不偿失,因此:C++11提供了override和final两个关键字,可以帮助用户检测是否重写。

🍒final:修饰虚函数,表示改虚函数不能被重写

final关键字要放在函数名的后面

class Car
{
public:
	virtual void Drive() final {}
};
class Benz :public Car
{
public:
    //此时会报错无法重写final修饰的函数
	virtual void Drive() { cout << "Benz-舒适" << endl; }
};

(个人感觉整个关键字意义不大...感觉虚函数不就是用来重写的嘛  ~0.o~  )

🍒override: 检查派生类虚函数是否重写了基类某个虚函数

用来做检查的,如果派生类的虚函数没有重写某个虚函数,则会进行报错

这也算是一个对虚函数重写的检验,毕竟如果你搞个虚函数,又不去重写,那是不是就没有必要去弄个这个虚函数了

该关键字也是要放在函数名的后面

class Car 
{
public:
	virtual void Drive() {}
};
class Benz :public Car 
{
public:
	virtual void Drive() override { cout << "Benz-舒适" << endl; }
};

🍉重载、重写(覆盖)、隐藏(重定义)的概念对比

 

3.抽象类

🍉概念:

  • 在虚函数的后面写上 =0 ,则这个函数为纯虚函数。
  • 包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。
  • 派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。
  • 纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承。
class Car
{
public:
	virtual void Drive() = 0;//纯虚函数(抽象类)的写法
};

class Benz :public Car
{
public:
	virtual void Drive()
	{
		cout << "Benz-舒适" << endl;
	}
};
class BMW :public Car
{
public:
	virtual void Drive()
	{
		cout << "BMW-操控" << endl;
	}
};

int main()
{
	Car* pBenz = new Benz;
	pBenz->Drive();

	Car* pBMW = new BMW;
	pBMW->Drive();

	return 0;
}

🍉接口继承和实现继承:

  • 普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实现。
  • 虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成多态,继承的是接口。
  • 所以如果不实现多态,不要把函数定义成虚函数。

4.多态的原理

🍉虚函数表

先来看一道例题:

// 这里常考一道题:sizeof(Base)是多少?
class Base
{
public:
	virtual void Func1()
	{
		cout << "Func1()" << endl;
	}
private:
	int _b = 1;
    char _ch;
};

int main()
{
	Base bb;
	cout << sizeof(Base) << endl;

	return 0;
}

我们根据内存对其原则,可能会想到sizeof(Base)应该是8(32位)

但实际上是12字节,这是因为虚函数内存中有一个虚函数表

对象中的这个指针我们叫做虚函数表指针(v代表virtual,f代表function)
一个含有虚函数的类中都至少都有一个虚函数表指针,因为虚函数的地址要被放到虚函数表中,虚函数表也简称虚表。
那么派生类中这个表放了些什么呢?我们接着往下分析

看下面代码进行理解:

  • 1.我们用一个派生列Son去继承Father
  • 2.Son中重写Func1
  • 3.Father中增加一个虚函数Func2和一个普通函数Func3
class Father
{
public:
	virtual void Func1()
	{
		cout << "Father::Func1()" << endl;
	}
	virtual void Func2()
	{
		cout << "Father::Func2()" << endl;
	}
	void Func3()
	{
		cout << "Father::Func3()" << endl;
	}
private:
	int _f = 1;
};

class Son : public Father
{
public:
	virtual void Func1()
	{
		cout << "Son::Func1()" << endl;
	}
private:
	int _s = 2;
};

int main()
{
	Father father;
	Son son;

	return 0;
}

通过观察和测试,我们发现了以下几点问题:

  • 1. 派生类对象son中也有一个虚表指针,son对象由两部分构成一部分是父类继承下来的成员,虚表指针的另一部分是自己的成员。
  • 2. 基类 father 对象和派生类 son 对象虚表是不一样的,这里我们发现Func1完成了重写,所以d的虚表中存的是重写的 Son::Func1,所以虚函数的重写也叫作覆盖
    覆盖就是指虚表中虚函数的覆盖,重写是语法的叫法,覆盖是原理层的叫法。
  • 3. 另外Func2继承下来后是虚函数,所以放进了虚表,Func3也继承下来了,但是不是虚函数,所以不会放进虚表。
  • 4. 虚函数表本质是一个存虚函数指针的指针数组,一般情况这个数组最后面放了一个nullptr。
  • 5. 总结一下派生类的虚表生成:
    • a)先将基类中的虚表内容拷贝一份到派生类虚表中
    • b)如果派生类重写了基类中某个虚函数,用派生类自己的虚函数覆盖虚表中基类的虚函数
    • c)派生类自己新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后。
  • 6. 这里还有一个很容易混淆的问题:虚函数存在哪的?虚表存在哪的?
    注意虚表存的是虚函数指针,不是虚函数,虚函数和普通函数一样的,都是存在代码段的
    只是他的指针又存到了虚表中。
    另外对象中存的不是虚表,存的是虚表指针。那么虚表存在哪的呢?
    不同编译器会不一样,我们去验证一下会发现vs下是存在代码段的

🍉多态的原理

看下面这段多态的代码: 

class Person 
{
	public:
		virtual void BuyTicket() { cout << "买票-全价" << endl; }
		virtual void Func() { cout << "买票-全价" << endl; }
	
		int _a = 0;
	
};
	
class Student : public Person 
{
public:
	virtual void BuyTicket() { cout << "买票-半价" << endl; }
	
	int _b = 1;
};
	
void Func(Person p)
{
	p.BuyTicket();
}
	
int main()
{
	Person mike;
	Func(mike);

	Student johnson;
	Func(johnson);

	return 0;
}

怎么做到指向父类调父类,指向子类调子类的?

父类对象的虚表存父类对象的虚函数,子类对象的虚表存子类对象的虚函数

🍉动态绑定与静态绑定

  • 1. 静态绑定又称为前期绑定(早绑定),在程序编译期间确定了程序的行为,也称为静态多态,比如:函数重载
  • 2. 动态绑定又称后期绑定(晚绑定),是在程序运行期间,根据具体拿到的类型确定程序的具体行为,调用具体的函数,也称为动态多态。

5.单继承和多继承的虚函数表

🍉单继承中的虚函数表

先来看下面一段代码:

class Base 
{ 
public :
 virtual void func1() { cout<<"Base::func1" <<endl;}
 virtual void func2() {cout<<"Base::func2" <<endl;}
private :
 int a;
};

class Derive :public Base 
{ 
public :
 virtual void func1() {cout<<"Derive::func1" <<endl;}
 virtual void func3() {cout<<"Derive::func3" <<endl;}
 virtual void func4() {cout<<"Derive::func4" <<endl;}
private :
 int b;
};

基类 Base 中有两个虚函数 func1() 和 func2() ,
其派生类 Derive 中有三个虚函数表其中  func1() 为虚函数的重写而func3() 和 func4() 是基类自己新加入的虚函数,那么此时func3() 和 func4()应该放在哪一个虚函数表中呢?

我们来看监视窗口,我们发现里面看不见func3和func4。
这里是编译器的监视窗口故意隐藏了这两个函数,也可以认为是他的一个小bug。
那么我们如何查看d的虚表呢?下面我们使用代码打印出虚表中的函数。

那么要怎么打印虚函数表呢?

思路:取出b、d对象的头4bytes,就是虚表的指针,前面我们说了虚函数表本质是一个存虚函数指针的指针数组,这个数组最后面放了一个nullptr

  •  1.先取b的地址,强转成一个int*的指针
  •  2.再解引用取值,就取到了b对象头4bytes的值,这个值就是指向虚表的指针
  •  3.再强转成VFPTR*,因为虚表就是一个存VFPTR类型(虚函数指针类型)的数组。
  •  4.虚表指针传递给PrintVTable进行打印虚表
  •  5.需要说明的是这个打印虚表的代码经常会崩溃,因为编译器有时对虚表的处理不干净,虚表最后面没有放nullptr,导致越界,这是编译器的问题。我们只需要点目录栏的-生成-清理解决方案,再编译就好了。
typedef void(*VFPTR) ();
void PrintVTable(VFPTR vTable[])
{
	// 依次取虚表中的虚函数指针打印并调用。调用就可以看出存的是哪个函数
	cout << " 虚表地址>" << vTable << endl;
	for (int i = 0; vTable[i] != nullptr; ++i)
	{
		printf(" 第%d个虚函数地址 :0X%x,->", i, vTable[i]);
		VFPTR f = vTable[i];
		f();
	}
	cout << endl;
}
int main()
{
	Base b;
	Derive d;
	VFPTR* vTableb = (VFPTR*)(*(int*)&b);
	PrintVTable(vTableb);
	VFPTR* vTabled = (VFPTR*)(*(int*)&d);
	PrintVTable(vTabled);
	return 0;
}

得到如下结果:

详细如下:

可以看到在继承之后,会有两个虚函数表,一个是基类的虚函数表,一个是派生类的基函数表,然后,派生类自己新加的虚函数,会放到派生类的虚函数表中。

🍉单继承中的虚函数表

下面代码中,用 Derive 来多继承 Base1和 Base2

class Base1 
{
public:
	virtual void func1() { cout << "Base1::func1" << endl; }
	virtual void func2() { cout << "Base1::func2" << endl; }
private:
	int b1;
};

class Base2 
{
public:
	virtual void func1() { cout << "Base2::func1" << endl; }
	virtual void func2() { cout << "Base2::func2" << endl; }
private:
	int b2;
	int bb;
};

class Derive : public Base2, public Base1 
{
public:
	virtual void func1() { cout << "Derive::func1" << endl; }
	virtual void func3() { cout << "Derive::func3" << endl; }
private:
	int d1;
};

// 用程序打印虚表
typedef void(*VFPTR) ();
void PrintVTable(VFPTR vTable[])
{
	cout << " 虚表地址>" << vTable << endl;
	for (int i = 0; vTable[i] != nullptr; ++i)
	{
		printf(" 第%d个虚函数地址 :0X%x,->", i, vTable[i]);
		VFPTR f = vTable[i];
		f();
	}
	cout << endl;
}

int main()
{
	Derive d;
	VFPTR* vTableb1 = (VFPTR*)(*(int*)&d);
	PrintVTable(vTableb1);
	VFPTR* vTableb2 = (VFPTR*)(*(int*)((char*)&d + sizeof(Base1)));
	PrintVTable(vTableb2);
	return 0;
}

观察结构可以看出:多继承派生类的未重写的虚函数放在第一个继承基类部分的虚函数表中

 

🍉菱形继承、菱形虚拟继承

实际中我们不建议设计出菱形继承及菱形虚拟继承,一方面太复杂容易出问题,另一方面这样的 模型,访问基类成员有一定得性能损耗。

所以菱形继承、菱形虚拟继承我们的虚表这里就不过多介绍了(本人能力有限),一般我们也不需要研究清楚,因为实际中很少用。

这里给大家两个链接,有兴趣的可以去看下面的这两篇文章。

1.C++虚函数表解析

2.C++对象的内存布局

6.继承和多态常见的面试问题:

🍉概念考察

1. 下面哪种面向对象的方法可以让你变得富有( ) 
        A: 继承 B: 封装 C: 多态 D: 抽象

2. ( )是面向对象程序设计语言中的一种机制。这种机制实现了方法的定义与具体的对象无关,而对方法的调用则可以关联于具体的对象。
        A: 继承 B: 模板 C: 对象的自身引用 D: 动态绑定

3. 面向对象设计中的继承和组合,下面说法错误的是?()
        A:继承允许我们覆盖重写父类的实现细节,父类的实现对于子类是可见的,是一种静态复用,也称为白盒复用
        B:组合的对象不需要关心各自的实现细节,之间的关系是在运行时候才确定的,是一种动态复用,也称为黑盒复用
        C:优先使用继承,而不是组合,是面向对象设计的第二原则
        D:继承可以使子类能自动继承父类的接口,但在设计模式中认为这是一种破坏了父类的封装性的表现

4. 以下关于纯虚函数的说法,正确的是( )
        A:声明纯虚函数的类不能实例化对象 
        B:声明纯虚函数的类是虚基类
        C:子类必须实现基类的纯虚函数 
        D:纯虚函数必须是空函数

5. 关于虚函数的描述正确的是( )
        A:派生类的虚函数与基类的虚函数具有不同的参数个数和类型 
        B:内联函数不能是虚函数
        C:派生类必须重新定义基类的虚函数 
        D:虚函数可以是一个static型的函数 

6. 关于虚表说法正确的是( )
        A:一个类只能有一张虚表
        B:基类中有虚函数,如果子类中没有重写基类的虚函数,此时子类与基类共用同一张虚表
        C:虚表是在运行期间动态生成的
        D:一个类的不同对象共享该类的虚表

7. 假设A类中有虚函数,B继承自A,B重写A中的虚函数,也没有定义任何虚函数,则( )
        A:A类对象的前4个字节存储虚表地址,B类对象前4个字节不是虚表地址
        B:A类对象和B类对象前4个字节存储的都是虚基表的地址
        C:A类对象和B类对象前4个字节存储的虚表地址相同
        D:A类和B类虚表中虚函数个数相同,但A类和B类使用的不是同一张虚表

8. 多继承中指针偏移问题?下面说法正确的是( )

class Base1 { public:  int _b1; };
class Base2 { public:  int _b2; };
class Derive : public Base1, public Base2 { public: int _d; };
int main() 
{
	Derive d;
	Base1* p1 = &d;
	Base2* p2 = &d;
	Derive* p3 = &d;

	return 0;
}

A:p1 == p2 == p3         B:p1 < p2 < p3         C:p1 == p3 != p2         D:p1 != p2 != p3

9.以下程序输出结果是什么()

class A
{
public:
	virtual void func(int val = 1) { std::cout << "A->" << val << std::endl; }
	virtual void test() { func(); }
};

class B : public A
{
public:
	void func(int val = 0) { std::cout << "B->" << val << std::endl; }
};

int main(int argc, char* argv[])
{
	B* p = new B;
	p->test();
	return 0;
}

A: A->0         B: B->1         C: A->1         D: B->0         E: 编译出错         F: 以上都不正确

答案选B,原因如下:

1.首先看上述代码构不构成多态,

        1)虚函数的重写,构成(三同不包含缺省值),子类继承了父类的virtual

        2)父类指针的调用,满足,p->test() 相当于把一个子类对象传个父类指针

2.构成多态,那我们去推,可能会觉得答案时D

        但是,要记得子类是会继承父类接口的,所以子类不光继承了父类的virtual,还继承了

        父类的缺省值,所以此时val=1

参考答案:

1. A           2. D           3. C           4. A           5. B  

6. D          7. D            8. A           9. C          10. B

🍉问答题:

1. 什么是多态?

2. 什么是重载、重写(覆盖)、重定义(隐藏)?

3. 多态的实现原理?

4. inline函数可以是虚函数吗?

答:可以,不过编译器就忽略inline属性,这个函数就不再是inline,因为虚函数要放到虚表中去。

5. 静态成员可以是虚函数吗?

答:不能,因为静态成员函数没有this指针,使用类型::成员函数的调用方式无法访问虚函数表,所以静态成员函数无法放进虚函数表。

6. 构造函数可以是虚函数吗?

答:不能,因为对象中的虚函数表指针是在构造函数初始化列表阶段才初始化的。

7. 析构函数可以是虚函数吗?什么场景下析构函数是虚函数?

答:可以,并且最好把基类的析构函数定义成虚函数。

8. 对象访问普通函数快还是虚函数更快?

答:首先如果是普通对象,是一样快的。如果是指针对象或者是引用对象,则调用的普通函数快,因为构成多态,运行时调用虚函数需要到虚函数表中去查找。

9. 虚函数表是在什么阶段生成的,存在哪的?

答:虚函数表是在编译阶段就生成的,一般情况下存在代码段(常量区)的。

10. C++菱形继承的问题?虚继承的原理?

答:这里不要把虚函数表虚基表搞混了。
虚函数表:实现多态
虚基表:解决菱形继承的冗余和二义性

菱形继承用的是虚继承来解决的,虚继承的原理就是利用了虚基表,来存储指向基类的偏移量

11. 什么是抽象类?抽象类的作用?

答:抽象类强制重写了虚函数,另外抽象类体现出了接口继承关系

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

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

相关文章

开源铸剑,五载匠心!Zilliz Cloud云服务盛装登场,引领向量数据库云时代!

2023 年注定是属于大模型和向量数据库的巅峰时刻。国内大模型的发展也迎来前所未有之机遇&#xff0c;“百模”激战正酣。在刚闭幕的世界人工智能大会上&#xff0c;国内外科技公司全线加入&#xff0c;三十余款大模型集中亮相&#xff0c;“国家队”尘埃落定&#xff0c;并正式…

接口测试(二) 优化项目分层及cookies值带入

整个项目分层如图 然后上代码 #data_test.py from openpyxl import load_workbook import json import osclass Date_test():filepath os.path.dirname(os.path.dirname(__file__))def __init__(self):self.case_id Noneself.url Noneself.data Noneself.Method Noneself…

模拟实现C++的string库的改进

之前写过&#xff0c;(8条消息) 模拟实现C的string库_Qianxueban的博客-CSDN博客 比较简单&#xff0c;我就直接截图的。我要改进一下。 1.改进string类中可以在字符串中存储\0 但我写的没有这项功能&#xff0c;究其根本就是代码我用的都是strcpy等等函数&#xff0c;应该用…

5G理论概述

文章目录 SA组网架构及协议栈4-5G核心网侧融合交互5G与4G用户标识5G网络网元和设备类型&#xff0c;接口1、AMF(Access and Mobility Management Function)&#xff0c;接入和移动管理功能2、SMF&#xff08;Session Management function&#xff09;&#xff0c;会话管理功能3…

MQTT快速入门

官网文档 前言&#xff1a; MQTT 是用于物联网连接的 OASIS 标准&#xff0c;它是一种基于发布订阅模式的、轻量级的消息传输协议&#xff0c;专为受限设备和低带宽、高延迟和不可靠的网络设计&#xff0c;并且能够提供一定的消息可靠性保证。得益于这些特性&#xff0c;MQTT…

在vite创建的vue3项目中使用Cesium标记地点(基于加载建筑样式,划分区域)

在vite创建的vue3项目中使用Cesium标记地点&#xff08;基于加载建筑样式&#xff0c;划分区域&#xff09; 使用vite创建vue3项目 npm create vitelatestcd到创建的项目文件夹中 npm install安装Cesium npm i cesium vite-plugin-cesium vite -D配置 vite.config.js文件&#…

通过平均列比较两组迭代次数

( A, B )---3*30*2---( 1, 0 )( 0, 1 ) 让网络的输入只有3个节点&#xff0c;AB训练集各由6张二值化的图片组成&#xff0c;让差值结构中有6个1, 行分布是0&#xff0c;1&#xff0c;1&#xff0c;1&#xff0c;1&#xff0c;2列分布是2&#xff0c;2&#xff0c;2.统计迭代次…

Java-多线程编程——基础篇及相关面试题

这里写目录标题 一、前言二、进程与线程的基本概念三、为什么Java中引入多线程&#xff1f;3.1 并行处理3.2 提高性能3.3 提高响应能力3.4 资源共享3.5 异步编程 四、Java多线程-创建多线程的类和接口4.1 Thread类4.2 Runnable接口 五、示例代码5.1 使用Thread类创建多线程六、…

【Django学习】(十二)GenericAPIView_过滤_排序_分页

上篇文章初步接触了GenericAPIView&#xff0c;这次来更加深入的学习它&#xff0c;了解里面的一些使用和方法 get_object&#xff1a;源码中&#xff1a;处理查询集&#xff0c;并含有所需要得pk值,lookup_fieldget_queryset&#xff1a;源码中&#xff1a;先判断queryset是否…

全志F1C200S嵌入式驱动开发(linux移植)

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】 前面完成了uboot移植,下面就要进行linux移植。当然,理论上uboot只是为后续的os准备好了一个基础运行环境,实际运行的操作系统是不是选择linux,也不一定。如果为了实际生产的需要…

Golang环境搭建指南(Windows和linux)

前言&#xff1a; go语言和Java&#xff0c;Python&#xff0c;C语言等等基本一样&#xff0c;也是需要在系统内集成语言环境的。语言基本都一样&#xff0c;支持各种系统架构&#xff0c;比如&#xff0c;mac&#xff0c;Windows&#xff0c;linux系统支持。本文仅以最为常用…

Django_Paginator分页器

目录 分页器代码说明 简单demo 源码等资料获取方法 分页器代码说明 import os import random # 需要导入分页器类from django.core.paginator import Paginator, EmptyPage# 导入配置django配置文件 os.environ.setdefault(DJANGO_SETTINGS_MODULE, dailyfresh.settings)it…

Java的Hibernate框架中集合类数据结构的映射编写教程

Java的Hibernate框架中集合类数据结构的映射编写教程 一、集合映射 1.集合小介 集合映射也是基本的映射&#xff0c;但在开发过程中不会经常用到&#xff0c;所以不需要深刻了解&#xff0c;只需要理解基本的使用方法即可&#xff0c;等在开发过程中遇到了这种问题时能够查询…

《机器学习公式推导与代码实现》chapter5-线性判别分析LDA

《机器学习公式推导与代码实现》学习笔记&#xff0c;记录一下自己的学习过程&#xff0c;详细的内容请大家购买作者的书籍查阅。 线性判别分析 线性判别分析(linear discriminant analysis, LDA)是一种经典的线性分类方法&#xff0c;其基本思想是将数据投影到低维空间&…

openGauss学习笔记-06 openGauss 基本概念

文章目录 openGauss学习笔记-06 openGauss 基本概念6.1 数据库&#xff08;Database&#xff09;6.2 数据块&#xff08;Block&#xff09;6.3 行&#xff08;Row&#xff09;6.4 列&#xff08;Cloumn&#xff09;6.5 表&#xff08;Table&#xff09;6.6 数据文件&#xff08…

Opencv之角点 Harris、Shi-Tomasi 检测详解

角点&#xff0c;即图像中某些属性较为突出的像素点 常用的角点有以下几种&#xff1a; 梯度最大值对应的像素点两条直线或者曲线的交点一阶梯度的导数最大值和梯度方向变化率最大的像素点一阶导数值最大&#xff0c;但是二阶导数值为0的像素点 API简介&#xff1a; void c…

Go语言网络编程:HTTP服务端之底层原理与源码分析——http.HandleFunc()、http.ListenAndServe()

一、启动 http 服务 import ("net/http" ) func main() {http.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request) {w.Write([]byte("ping...ping..."))})http.ListenAndServe(":8999", nil) }在 Golang只需要几行代…

MySQL存储过程和存储函数练习

创建表并插入数据 字段名 数据类型 主键 外键 非空 唯一 自增 id INT 是 否 是 是 否 name VARCHAR(50) 否 否 是 否 否 glass VARCHAR(50) 否 否 是 否 否 sch 表内容 id name glass 1 xiaommg glass 1 2 xiaojun glass 2 1、创建一个可以统计表格内记录条数的存储函数 &#…

耳夹式骨传导耳机哪个牌子好?耳夹骨传导耳机推荐

骨传导耳机品牌越来越多&#xff0c;选择骨传导耳机时可不是一件简单的事&#xff0c;在挑选的时候首先需要考虑到耳机自身的综合性能&#xff0c;以及耳机的配置如何都会影响到我们使用耳机的幸福感&#xff0c;接下来我来给大家挑选几款目前口碑不错的耳夹式骨传导耳机&#…

windows下使用cd命令切换到D盘的方法

windows下使用cd命令切换到D盘的方法 winr输入cmd进入终端