类和对象(友元、运算符重载、继承、多态)---C++

类和对象

  • 4.友元
    • 4.1全局函数做友元
    • 4.2类做友元
    • 4.3成员函数做友元
  • 5.运算符重载
    • 5.1 加号运算符重载
      • 5.1.1成员函数实现运算符重载
      • 5.1.2全局函数实现运算符重载
    • 5.2 左移运算符重载
      • 5.2.1全局函数实现运算符重载
      • 5.2.2成员函数实现运算符重载
    • 5.3 递增/递减运算符重载
      • 5.3.1 前置++
        • 5.3.1.1成员函数实现运算符重载
        • 5.3.1.2全局函数实现运算符重载
      • 5.3.2 后置++
        • 5.3.2.1成员函数实现运算符重载
        • 5.3.2.2全局函数实现运算符重载
      • 5.3.3 前置--
        • 5.3.3.1成员函数实现运算符重载
        • 5.3.3.2全局函数实现运算符重载
      • 5.3.4 后置--
        • 5.3.4.1成员函数实现运算符重载
        • 5.3.4.2全局函数实现运算符重载
    • 5.4 赋值运算符重载
    • 5.5 关系运算符重载
      • 5.5.1 关系运算符(==)重载
      • 5.5.2 关系运算符(>)重载
    • 5.6 函数调用运算符重载
  • 6.继承
    • 6.1 继承的基本语法
    • 6.2继承的方式
    • 6.3继承中的对象模型
    • 6.4继承中的构造和析构顺序
    • 6.5继承同名成员处理方式
      • 6.5.1继承非静态同名成员处理方式
        • 6.5.1.1非静态同名成员变量处理方式
        • 6.5.1.2非静态同名成员函数处理方式
      • 6.5.2继承同名静态成员处理方式
        • 6.5.2.1同名静态成员变量处理方式
        • 6.5.2.2同名静态成员函数处理方式
    • 6.6多继承语法
    • 6.7菱形继承
  • 7.多态
    • 7.1 多态的基本应用
      • 7.1.1 多态的基本概念
      • 7.1.2 多态的基本原理
    • 7.2 纯虚函数和抽象类
    • 7.3 虚析构和纯虚析构

在这里插入图片描述

4.友元

在程序里,有些私有属性也想让类外特殊的一些函数或者类进行访问,就需要用到友元的技术;

友元的目的就是让一个函数或者类访问另一个类中私有成员
友元的关键字为 friend

友元的三种实现

  • 全局函数做友元
  • 类做友元
  • 成员函数做友元

4.1全局函数做友元

class Building
{
	//告诉编译器 goodGay全局函数 是 Building类的好朋友,可以访问类中的私有内容
	friend void goodGay(Building* building);

public:

	Building()
	{
		m_SittingRoom = "客厅";
		m_BedRoom = "卧室";
	}

public:
	//公共权限
	string m_SittingRoom; //客厅

private:
	//私有权限
	string m_BedRoom; //卧室
};

//全局函数
void goodGay(Building* building)
{
	cout << "好朋友正在访问: " << building->m_SittingRoom << endl;
	cout << "好朋友正在访问: " << building->m_BedRoom << endl;
}

void test01()
{
	Building b;
	goodGay(&b);
}

在这里插入图片描述
friend void goodGay(Building* building);

  • 告诉编译器 goodGay全局函数 是 Building类的好朋友,可以访问类中的私有内容

可见,调用test01函数,当全局函数做友元时,该全局函数可以访问私有权限内容。

4.2类做友元

class Building;//先声明,防止goodGay类出错
class goodGay
{
public:
	//类内声明,类外实现
	goodGay();
	void visitor();
	~goodGay();

private:
	Building* building;
};

class Building
{
	//告诉编译器 goodGay类是Building类的好朋友,可以访问到Building类中私有内容
	friend class goodGay;

public:
	Building();

public:
	string m_SittingRoom; //客厅
private:
	string m_BedRoom;//卧室
};

//类外实现成员函数(注意加上所在类空间)
Building::Building()//Building类构造函数
{
	this->m_SittingRoom = "客厅";
	this->m_BedRoom = "卧室";
}
goodGay::goodGay()//goodGay类构造函数
{
	//堆区开辟空间,注意要释放(析构函数释放,delete)
	building = new Building;
}
goodGay::~goodGay()
{
	if(building!=NULL)
	{
		delete building;
		building=NULL;
	}
}
void goodGay::visitor()
{
	cout << "好朋友正在访问" << building->m_SittingRoom << endl;
	cout << "好朋友正在访问" << building->m_BedRoom << endl;
}

void test01()
{
	goodGay gg;
	gg.visitor();

}

在这里插入图片描述
== friend class goodGay;==

  • 告诉编译器 goodGay类是Building类的好朋友,可以访问到Building类中私有内容

可见,调用test01函数后,当类做友元时,该类内可以访问另一个类内的私有权限内容。

4.3成员函数做友元

class Building;//先声明,防止goodGay类出错
class goodGay
{
public:

	goodGay();
	void visitor1(); //只让visitor1函数作为Building的好朋友,可以发访问Building中私有内容
	void visitor2();

private:
	Building* building;
};

class Building
{
	//告诉编译器  goodGay类中的visitor1成员函数 是Building好朋友,可以访问私有内容
	friend void goodGay::visitor1();

public:
	Building();

public:
	string m_SittingRoom; //客厅
private:
	string m_BedRoom;//卧室
};

Building::Building()
{
	this->m_SittingRoom = "客厅";
	this->m_BedRoom = "卧室";
}
goodGay::goodGay()
{
	building = new Building;
}

void goodGay::visitor1()
{
	cout << "好朋友1正在访问" << building->m_SittingRoom << endl;
	cout << "好朋友1正在访问" << building->m_BedRoom << endl;
}
void goodGay::visitor2()
{
	cout << "好朋友2正在访问" << building->m_SittingRoom << endl;
	//cout << "好朋友正在访问" << building->m_BedRoom << endl;//无法访问
}

void test01()
{
	goodGay  gg;
	gg.visitor1();
	gg.visitor2();

}

在这里插入图片描述
friend void goodGay::visitor1();

  • 告诉编译器 goodGay类中的visitor1成员函数 是Building好朋友,可以访问私有内容

可见,调用test01函数后,当一个类内的成员函数做另一个类的友元时,该类内的成员函数可以访问另一个类内的私有权限内容。

5.运算符重载

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

5.1 加号运算符重载

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

对于内置数据类型,编译器知道如何进行运算:
例如两个整型数据的相加,可以直接应用,但对于一些非内置数据类型,比如两个对象的相加,编译器内部没有相关运算方式,故需要我们自己对运算符进行补充。

如果不进行运算符重载,就会出现下面类似的错误:
在这里插入图片描述

5.1.1成员函数实现运算符重载

实例:实现两个对象的相加

class Person {
public:
	Person() {};//为了提供对无参构造函数的调用
	//若无,自己定义了有参构造函数,系统默认无无参构造函数
	//则Person temp;无法成立
	Person(int a, int b)//有参构造
	{
		this->m_A = a;
		this->m_B = b;
	}
	//成员函数实现 + 号运算符重载
	//函数名operator+系统默认,可以实现简化调用
	//自己定义函数名也可,但无法实现下面的简化调用
	Person operator+(const Person& p) {
		Person temp;//无参构造
		temp.m_A = this->m_A + p.m_A;
		temp.m_B = this->m_B + p.m_B;
		return temp;//值返回(重新创建新对象)
	}

public:
	int m_A;
	int m_B;
};

//运算符重载 可以发生函数重载 
//同一函数名表示不同运算
Person operator+(const Person& p2, int val)
{
	Person temp;
	temp.m_A = p2.m_A + val;
	temp.m_B = p2.m_B + val;
	return temp;
}

//测试函数
void test() {

	Person p1(10, 10);
	Person p2(20, 20);

	//成员函数方式
	//本质调用为:
	//Person p3=p2.operaor+(p1)
	Person p3 = p2 + p1; 
	cout << "mA:" << p3.m_A << " mB:" << p3.m_B << endl;

	//本质调用为:
	//Person p4=operaor+(p3,10)
	Person p4 = p3 + 10; 
	cout << "mA:" << p4.m_A << " mB:" << p4.m_B << endl;

}

在这里插入图片描述

5.1.2全局函数实现运算符重载

示例:

class Person {
public:
	Person(int a, int b)//有参构造
	{
		this->m_A = a;
		this->m_B = b;
	}

public:
	int m_A;
	int m_B;
};

//全局函数实现 + 号运算符重载
//对象+对象
Person operator+(const Person & p1, const Person & p2) {
	Person temp(0, 0);
	temp.m_A = p1.m_A + p2.m_A;
	temp.m_B = p1.m_B + p2.m_B;
	return temp;
}
//运算符重载 可以发生函数重载 
//对象+int
Person operator+(const Person& p2, int val)
{
	Person temp(0,0);
	temp.m_A = p2.m_A + val;
	temp.m_B = p2.m_B + val;
	return temp;
}

void test() {

	Person p1(10, 10);
	Person p2(20, 20);

	//成员函数方式
	//本质实现:
	Person p3 = operator+ (p1, p2);
	//Person p3 = p2 + p1;  
	cout << "mA:" << p3.m_A << " mB:" << p3.m_B << endl;

	//本质实现:Person p4 = operator+ (p3, 10);
	Person p4 = p3 + 10; 
	cout << "mA:" << p4.m_A << " mB:" << p4.m_B << endl;
}

在这里插入图片描述

总结1:对于内置的数据类型的表达式的的运算符是不可能改变的;
总结2:不要滥用运算符重载。(即不可命名为加号运算符重载,实现用减法)

5.2 左移运算符重载

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

5.2.1全局函数实现运算符重载

示例:

class Person {
	//友元:实现对私有权限成员的访问
	friend ostream& operator<<(ostream& out, Person& p);

public:

	Person(int a, int b)
	{
		this->m_A = a;
		this->m_B = b;
	}

	

private:
	int m_A;
	int m_B;
};

//全局函数实现左移重载
//ostream对象只能有一个
ostream& operator<<(ostream& out, Person& p) {
	out << "a:" << p.m_A << " b:" << p.m_B;
	return out;
}

void test() {

	Person p1(10, 20);
	cout << p1 << "hello world" << endl; //链式编程
}

在这里插入图片描述

5.2.2成员函数实现运算符重载

示例:

  • 1.对象本身做形参
class Person {

public:

	Person(int a, int b)
	{
		this->m_A = a;
		this->m_B = b;
	}

	//成员函数 实现左移运算符重载,可以实现,但不是我们想要的效果
	ostream& operator<<(Person& p){
		cout << "a:" << p.m_A << " b:" << p.m_B;
		return cout;//链式编程
	}	
private:
	int m_A;
	int m_B;
};

void test() {

	Person p1(10, 20);
	p1.operator<<(p1)<<endl;
	//简化
	p1 << p1 << " hello world" <<endl;//与内置函数实现不一致
}

在这里插入图片描述

  • 2.标准输出流做形参

示例:

class Person {

public:

	Person(int a, int b)
	{
		this->m_A = a;
		this->m_B = b;
	}

	//成员函数 实现左移运算符重载,可以实现,但不是我们想要的效果
	ostream& operator<<(ostream &out) {
		out << "a:" << this->m_A << " b:" << this->m_B;;
		return cout;//链式编程
	}

private:
	int m_A;
	int m_B;
};

void test() {

	Person p1(10, 20);
	//本质实现:
	//p1.operator<<(cout);
	p1 << cout << " hello world" << endl;//可见与内置函数输出不一致
}

利用成员函数重载左移运算符,无法实现与内置输出一致的顺序(即cout<<p,cout在左侧),故不会利用成员函数重载<<运算符。

总结:重载左移运算符配合友元可以实现输出自定义数据类型

5.3 递增/递减运算符重载

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

5.3.1 前置++

5.3.1.1成员函数实现运算符重载

示例:

class MyInteger {

	friend ostream& operator<<(ostream& out, MyInteger myint);

public:
	MyInteger() {
		m_Num = 0;
	}
	//前置++
	//局部函数实现
	//返回引用
	MyInteger& operator++() {
		//先++
		m_Num++;
		//再返回
		return *this;//返回对象本身(引用),实现对一直对一个对象进行递增操作
	}

private:
	int m_Num;
};

//左移运算符重载:全局函数
ostream& operator<<(ostream& out, MyInteger myint) {
	out << myint.m_Num;
	return out;
}

//前置++ 先++ 再返回
void test01() {
	MyInteger myInt;
	cout << ++(++myInt) << endl;
	cout << myInt << endl;
//本质实现:
//operator<<(cout, myInt.operator++())<<endl;//相当于cout << ++myInt << endl;
//cout << myInt.operator++().operator++() << endl;//相当于cout << ++(++myInt) << endl;
}

分析:++myInt:m_Num=1(返回对象本身);
++(++myInt)(对同一个对象++) :m_Num=2(返回对象本身);

在这里插入图片描述

  • 如果将返回值改为值返回:即
//值返回
MyInteger operator++() {
	//先++
	m_Num++;
	//再返回
	return *this;//拷贝一个新对象
}

分析:++myInt(第一次:对象本身++):m_Num=1(创建新对象);
++(++myInt)(对新对象++):m_Num=2(返回新对象);

  • 即cout << ++(++myInt) << endl中的++(++myInt)不再是原对象,而是创建的第二个新对象(其内容和返回对象本身结束的时候一样);
    cout << myInt << endl;输出对象本身。

在这里插入图片描述
可见,返回类型不同,最后结果不同。

  • 返回对象本身(引用):实现对一直对一个对象进行递增操作,每一次++都对同一个进行运算。
  • 值返回方式:第一次调用++是对对象本身进行,但之后回利用拷贝构造函数创建一个新的对象,就形成了每次调用都会形成一个新对象,无法实现如同第一种(返回引用)的对同一个对象持续累加。
5.3.1.2全局函数实现运算符重载
MyInteger& operator++(MyInteger &myInt) {
	//先++
	myInt.m_Num++;
	//再返回
	return myInt;//返回对象本身(引用)
}

注:全局函数下,要实现链式访问只能返回对象本身(返回引用);

5.3.2 后置++

在这里插入图片描述
对于后置++,无法实现链式编程。故对于后置++的重载也无法实现链式编程。
报错原因:表达式必须是可修改的左值。

5.3.2.1成员函数实现运算符重载

示例:

class MyInteger {

	friend ostream& operator<<(ostream& out, MyInteger myint);
public:
	MyInteger() {
		m_Num = 0;
	}
	
	//后置++
	MyInteger operator++(int) {
		//先返回
		MyInteger temp = *this; //记录当前本身的值,然后让本身的值加1,但是返回的是以前的值,达到先返回后++;
		m_Num++;
		return temp;
	}

private:
	int m_Num;
};
//左移运算符重载:全局函数
ostream& operator<<(ostream& out, MyInteger myint) {
	out << myint.m_Num;
	return out;
}

//后置++ 先返回 再++
void test02() {

	MyInteger myInt;
	cout << myInt++ << endl;
	cout << myInt << endl;
	//本质实现:
	//operator<<(cout, myInt.operator++(0))<<endl;
	//operator<<(cout, myInt)<<endl;
}

在这里插入图片描述

MyInteger operator++(int):int-占位参数

5.3.2.2全局函数实现运算符重载

示例:

//int为占位参数,为了和前置++区分;调用时需补占位参数(任意数都可)
MyInteger operator++(MyInteger& myInt,int) {
	//先返回
	MyInteger temp = myInt; //记录当前本身的值,然后让本身的值加1,但是返回的是以前的值,达到先返回后++;
	myInt.m_Num++;
	return temp;
}

//后置++ 先返回 再++
void test02() {

	MyInteger myInt;
	cout << myInt++ << endl;
	cout << myInt << endl;
	// 
	//本质实现:
	//operator<<(cout, operator++(myInt,0)) << endl;
	//operator<<(cout, myInt)<<endl;

}

5.3.3 前置–

5.3.3.1成员函数实现运算符重载

参考前置++:(前置–可以实现链式编程,只列写返回对象本身)

MyInteger& operator--() {
	//先--
	m_Num--;
	//再返回
	return *this;//返回对象本身(引用),实现对一直对一个对象进行递增操作
}
5.3.3.2全局函数实现运算符重载
MyInteger& operator--(MyInteger& myInt) {
	//先++
	myInt.m_Num--;
	//再返回
	return myInt;//返回对象本身(引用)
}

5.3.4 后置–

参见后置++:

5.3.4.1成员函数实现运算符重载
//后置--
MyInteger& operator--(int) {
	//先返回
	MyInteger temp = *this; //记录当前本身的值,然后让本身的值减1,但是返回的是以前的值,达到先返回后--;
	m_Num--;
	return temp;
}
5.3.4.2全局函数实现运算符重载
//int为占位参数,为了和前置--区分;调用时需补占位参数(任意数都可)
MyInteger operator--(MyInteger& myInt,int) {
	//先返回
	MyInteger temp = myInt; //记录当前本身的值,然后让本身的值减1,但是返回的是以前的值,达到先返回后--;
	myInt.m_Num--;
	return temp;
}

递增/递减运算符重载总结: 前置递增返回引用,后置递增返回值.

5.4 赋值运算符重载

c++编译器至少给一个类添加4个函数:

  1. 默认构造函数(无参,函数体为空)
  2. 默认析构函数(无参,函数体为空)
  3. 默认拷贝构造函数,对属性进行值拷贝
  4. 赋值运算符 operator=, 对属性进行值拷贝

前三个之前介绍过,此处着重介绍第四个:
如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝问题 。
深浅拷贝

示例:

class Person
{
public:

	Person(int age)
	{
		//将年龄数据开辟到堆区
		m_Age = new int(age);
	}

	//重载赋值运算符 
	Person& operator=(Person& p)
	{
		//判断是否有属性在堆区,若有先释放干净,再进行深拷贝
		if (m_Age != NULL)
		{
			delete m_Age;
			m_Age = NULL;
		}
		//编译器提供的代码是浅拷贝
		//m_Age = p.m_Age;

		//提供深拷贝 解决浅拷贝的问题
		m_Age = new int(*p.m_Age);

		//返回自身
		return *this;//链式编程
	}


	~Person()
	{
		//释放堆区空间
		if (m_Age != NULL)
		{
			delete m_Age;
			m_Age = NULL;
		}
	}

	//年龄的指针
	int* m_Age;

};
void test01()
{
	Person p1(18);
	Person p2(20);
	Person p3(30);

	p3 = p2 = p1; //赋值操作:链式编程

	cout << "p1的年龄为:" << *p1.m_Age << endl;
	cout << "p2的年龄为:" << *p2.m_Age << endl;
	cout << "p3的年龄为:" << *p3.m_Age << endl;
}

在这里插入图片描述
附:
参照内置类型赋值的链式程序。

int a = 10;
int b = 20;
int c = 30;

c = b = a;
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl;

在这里插入图片描述

5.5 关系运算符重载

作用:重载关系运算符,可以让两个自定义类型对象进行对比操作。

5.5.1 关系运算符(==)重载

示例:

class Person
{
public:
	Person(string name, int age)
	{
		this->m_Name = name;
		this->m_Age = age;
	};

	bool operator==(Person& p)
	{
		if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
		{
			return true;
		}
		else
		{
			return false;
		}
	}

	string m_Name;
	int m_Age;
};

void test01()
{

	Person a("Tom", 18);
	Person b("Tom", 25);

	if (a == b)
	{
		cout << "a和b相等" << endl;
	}
	else
	{
		cout << "a和b不相等" << endl;
	}

}

在这里插入图片描述

5.5.2 关系运算符(>)重载

示例:

class Person
{
public:
	Person(string name, int age)
	{
		this->m_Name = name;
		this->m_Age = age;
	};

	int operator>(Person& p)
	{
	//按字母进行比较
		return this->m_Name.compare(p.m_Name);
		//compare按照每一个字母的ASCII值进行比较,根据结果返回0,大于0,小于0
	}

	string m_Name;
	int m_Age;
};

void test01()
{

	Person a("Tom", 18);
	Person b("Marry", 25);

	if ((a > b) == 0)
	{
		cout << "a==b" << endl;
	}
	else if ((a > b) > 0)
	{
		cout << "a>b" << endl;
	}
	else
		cout << "a<b" << endl;
}

在这里插入图片描述

5.6 函数调用运算符重载

  • 函数调用运算符 () 也可以重载;
  • 由于重载后使用的方式非常像函数的调用,因此称为仿函数;
  • 仿函数没有固定写法,非常灵活。

示例:

class MyAdd
{
public:
	int operator()(int v1, int v2)
	{
		return v1 + v2;
	}
};

void test01()
{
	MyAdd add;
	int ret = add(10, 10);//相当于:int ret = add.operator()(10, 10)
	cout << "ret = " << ret << endl;

	//匿名对象调用  
	cout << "MyAdd()(100,100) = " << MyAdd()(100, 100) << endl;
}

在这里插入图片描述

6.继承

继承是面向对象三大特性之一

有些类与类之间存在特殊的关系,例如下图中:
在这里插入图片描述
猫和狗都具备动物的属性,同时其又有很多品种(自己的属性)。

我们发现,定义这些类时,下级别的成员除了拥有上一级的共性,还有自己的特性。
这个时候我们就可以考虑利用继承的技术,减少重复代码

6.1 继承的基本语法

继承的语法:class 子类 : 继承方式 父类
借助下面的事例,介绍继承的优势和语法:

例如:
我们看到很多网站(以某网站编程培训为例)中,都有公共的头部,公共的底部,甚至公共的左侧列表,只有中心内容不同;接下来我们分别利用普通写法和继承的写法来实现网页中的内容,看一下继承存在的意义以及好处:

普通实现:

//Java页面
class Java 
{
public:
	void header()
	{
		cout << "首页、公开课、登录、注册...(公共头部)" << endl;
	}
	void footer()
	{
		cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
	}
	
	void content()
	{
		cout << "JAVA学科视频" << endl;
	}
};
//Python页面
class Python
{
public:
	void header()
	{
		cout << "首页、公开课、登录、注册...(公共头部)" << endl;
	}
	void footer()
	{
		cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
	}
	
	void content()
	{
		cout << "Python学科视频" << endl;
	}
};
//C++页面
class CPP 
{
public:
	void header()
	{
		cout << "首页、公开课、登录、注册...(公共头部)" << endl;
	}
	void footer()
	{
		cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
	}
	
	void content()
	{
		cout << "C++学科视频" << endl;
	}
};

//测试函数
void test01()
{
	//Java页面
	cout << "Java下载视频页面如下: " << endl;
	Java ja;
	ja.header();
	ja.footer();
	ja.content();
	cout << "--------------------" << endl;

	//Python页面
	cout << "Python下载视频页面如下: " << endl;
	Python py;
	py.header();
	py.footer();
	py.content();
	cout << "--------------------" << endl;

	//C++页面
	cout << "C++下载视频页面如下: " << endl;
	CPP cp;
	cp.header();
	cp.footer();
	cp.content();

}

可见,对于上述代码,有一部分代码多次重复引用,虽然结构清晰,但会造成代码冗余,内存浪费。
借助继承的特性可以实现简化:

继承实现:

//公共页面
class BasePage
{
public:
	void header()
	{
		cout << "首页、公开课、登录、注册...(公共头部)" << endl;
	}

	void footer()
	{
		cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
	}

};

//Java页面
class Java : public BasePage
{
public:
	void content()
	{
		cout << "JAVA学科视频" << endl;
	}
};
//Python页面
class Python : public BasePage
{
public:
	void content()
	{
		cout << "Python学科视频" << endl;
	}
};
//C++页面
class CPP : public BasePage
{
public:
	void content()
	{
		cout << "C++学科视频" << endl;
	}
};

在这里插入图片描述
将普通实现的每个类内容替换,测试函数不变,会实现如上的结果。

总结:

继承的好处:可以减少重复的代码
class A : public B
A 类称为子类 或 派生类
B 类称为父类 或 基类

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

一类是从基类继承过来的,一类是自己增加的成员
从基类继承过过来的表现其共性,而新增的成员体现了其个性

6.2继承的方式

继承的语法:class 子类 : 继承方式 父类

继承方式一共有三种:

  • 公共继承
  • 保护继承
  • 私有继承
    不同继承方式下,对于父类的不同权限的内容的访问条件,可以用下面的图进行说明。
    在这里插入图片描述
    可联系,封装权限中的保护权限和私有权限的区别:
  • protected 保护权限 :类内可以访问 类外不可以访问(例如:儿子可以访问到父亲中的保护内容)
  • private 私有权限 :类内可以访问 类外不可以访问(例如:儿子不可以访问到父亲中的私有内容)

6.3继承中的对象模型

问题:
从父类继承过来的成员,哪些属于子类对象中?
或者说子类的大小对父类中的继承权限有无关系?

示例:

class Base
{
public:
	int m_A;
protected:
	int m_B;
private:
	int m_C; //私有成员只是被隐藏了,但是还是会继承下去
};

//公共继承
class Son :public Base
//对于父类中的公共权限和保护权限,可访问,不可访问私有权限
{
public:
	int m_D;
};

void test01()
{
	cout << "sizeof Son = " << sizeof(Son) << endl;
}

在这里插入图片描述
可见,虽然子类无法访问父类中的私有权限内容,但子类大小是包含父类中的私有权限的。
下面借助VS自带的开发人员命令提示符窗口,对上述内容进行一个深入介绍:

在这里插入图片描述
由上图可知,对于Son类中,包含父类的全部内容(三种权限内容都被继承下来)和自己的特有内容,虽然对于公共继承而言,父类中私有权限无法访问,但其也被子类继承,只是被编译器隐藏。

6.4继承中的构造和析构顺序

子类继承父类后,当创建子类对象,也会调用父类的构造函数;
问题:父类和子类的构造和析构顺序是谁先谁后?

  • 继承中 先调用父类构造函数,再调用子类构造函数,析构顺序与构造相反。
    示例:
class Base
{
public:
	Base()
	{
		cout << "Base构造函数!" << endl;
	}
	~Base()
	{
		cout << "Base析构函数!" << endl;
	}
};

class Son : public Base
{
public:
	Son()
	{
		cout << "Son构造函数!" << endl;
	}
	~Son()
	{
		cout << "Son析构函数!" << endl;
	}

};

void test01()
{
	Son s;
}

在这里插入图片描述

总结:继承中 先调用父类构造函数,再调用子类构造函数,析构顺序与构造相反。
(可对比类对象作为类成员的构造和析构函数调用顺序)2.7类对象作为类成员

6.5继承同名成员处理方式

6.5.1继承非静态同名成员处理方式

问题:当子类与父类出现同名的成员,如何通过子类对象,访问到子类或父类中同名的数据呢?

  • 访问子类同名成员 直接访问即可;
  • 访问父类同名成员 需要加作用域
6.5.1.1非静态同名成员变量处理方式

示例:

//父类
class Base {
public:
	Base()
	{
		m_A = 100;
	}
	
public:
	int m_A;
};

//子类
class Son : public Base {
public:
	Son()
	{
		m_A = 200;
	}
public:
	int m_A;
};

void test01()
{
	//子类和父类都有m_A成员变量
	Son s;
	cout << "Son类下m_A:" << s.m_A << endl;

	cout << "Base类下m_A:" << s.m_A << endl;
	cout << "Base类下m_A:" << s.Base::m_A << endl;
}

在这里插入图片描述
可见,子类和父类中有同名成员变量时,如要访问父类中成员变量需要加上父类所在作用域。

6.5.1.2非静态同名成员函数处理方式

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

示例:

//父类
class Base {
public:
	
	void func()
	{
		cout << "Base - func()调用" << endl;
	}

	void func(int a)
	{
		cout << "Base - func(int a)调用" << endl;
	}

public:
	int m_A;
};

//子类
class Son : public Base {
public:
	void func()
	{
		cout << "Son - func()调用" << endl;
	}
public:
	int m_B;
};

void test02()
{
	Son s;

	s.func();
	//s.func(10);//报错
	s.Base::func();
	s.Base::func(10);
}

在这里插入图片描述
总结:

  1. 子类对象可以直接访问到子类中同名成员
  2. 子类对象加作用域可以访问到父类同名成员
  3. 当子类与父类拥有同名的成员函数,子类会隐藏父类中同名成员函数,加作用域可以访问到父类中同名函数

6.5.2继承同名静态成员处理方式

问题:继承中同名的静态成员在子类对象上如何进行访问?

静态成员和非静态成员出现同名,处理方式一致

  • 访问子类同名成员 直接访问即可;
  • 访问父类同名成员 需要加作用域
6.5.2.1同名静态成员变量处理方式

示例:

class Base {
public:
	static int m_A;
};

//类内声明,类外初始化
int Base::m_A = 100;

class Son : public Base {
public:
	static int m_A;
};
//类内声明,类外初始化
int Son::m_A = 200;

//同名成员属性
void test01()
{
	//通过对象访问
	cout << "通过对象访问: " << endl;
	Son s;
	cout << "Son  下 m_A = " << s.m_A << endl;
	cout << "Base 下 m_A = " << s.Base::m_A << endl;

	//通过类名访问
	cout << "通过类名访问: " << endl;
	cout << "Son  下 m_A = " << Son::m_A << endl;
	cout << "Base 下 m_A = " << Son::Base::m_A << endl;
}

在这里插入图片描述
对于静态同名成员变量,子类和父类中成员变量的访问有两种方式:

  • 通过对象访问:子类对象直接访问子类同名成员,父类成员访问需要子类对象加上父类作用域;
  • 通过类名访问:子类对象可以直接在子类类名直接访问,父类成员需要在子类对象类名的基础上加上父类的作用域。
    • Son::Base::m_A 中第一个::代表通过类名方式访问;第二个::代表访问父类作用域下。
6.5.2.2同名静态成员函数处理方式

同同名静态成员变量访问一样。
示例:

class Base {
public:
	static void func()
	{
		cout << "Base - static void func()" << endl;
	}
	static void func(int a)
	{
		cout << "Base - static void func(int a)" << endl;
	}

	static int m_A;
};

class Son : public Base {
public:
	static void func()
	{
		cout << "Son - static void func()" << endl;
	}
	static int m_A;
};

//同名成员函数
void test02()
{
	//通过对象访问
	cout << "通过对象访问: " << endl;
	Son s;
	s.func();
	s.Base::func();
	s.Base::func(10);

	//通过类名访问
	cout << "通过类名访问: " << endl;
	Son::func();
	Son::Base::func();
	//出现同名,子类会隐藏掉父类中所有同名成员函数,需要加作作用域访问
	Son::Base::func(100);
}

在这里插入图片描述

总结:同名静态成员处理方式和非静态处理方式一样,只不过有两种访问的方式(通过对象 和 通过类名)

大总结:

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

6.6多继承语法

C++允许一个类继承多个类
语法: class 子类 :继承方式 父类1 , 继承方式 父类2...

多继承可能会引发父类中有同名成员出现,需要加作用域区分。
注:C++实际开发中不建议用多继承

示例:

//父类1
class Base1 {
public:
	Base1()
	{
		m_A = 100;
	}
public:
	int m_A;
};

//父类2
class Base2 {
public:
	Base2()
	{
		m_A = 200;  
	}
public:
	//同名成员变量
	int m_A;
};

//语法:class 子类:继承方式 父类1 ,继承方式 父类2 
class Son : public Base2, public Base1
{
public:
	Son()
	{
		m_C = 300;
		m_D = 400;
	}
public:
	int m_C;
	int m_D;
};

//多继承容易产生成员同名的情况
//通过使用类名作用域可以区分调用哪一个基类的成员
void test01()
{
	Son s;
	cout << "sizeof Son = " << sizeof(s) << endl;
	cout << s.Base1::m_A << endl;
	cout << s.Base2::m_A << endl;
}

在这里插入图片描述
下面借助VS自带的开发人员命令提示符窗口,对上述内容进行一个深入介绍:
在这里插入图片描述

总结: 多继承中如果父类中出现了同名情况,子类使用时候要加作用域

6.7菱形继承

菱形继承概念:
​ 两个派生类继承同一个基类;
​ 又有某个类同时继承者两个派生类;
​ 这种继承被称为菱形继承,或者钻石继承。
典型的菱形继承案例:
在这里插入图片描述
在上图中,羊和驼都继承了动物中的属性,同时羊驼又分别继承两者的属性,就会造成羊驼中重复包含动物属性,造成浪费。即:

菱形继承问题:

  1. 羊继承了动物的数据,驼同样继承了动物的数据,当草泥马使用数据时,就会产生二义性(不知道继承谁的)。
  2. 草泥马继承自动物的数据继承了两份,其实我们应该清楚,这份数据我们只需要一份就可以。

普通示例:

class Animal
{
public:
	int m_Age;
};

class Sheep :  public Animal {};
class camel :  public Animal {};//camel-骆驼
class alpaca : public Sheep, public camel {};//alpaca-羊驼

void test01()
{
	alpaca st;
	st.Sheep::m_Age = 100;
	st.camel::m_Age = 200;

	cout << "st.Sheep::m_Age = " << st.Sheep::m_Age << endl;
	cout << "st.camel::m_Age = " << st.camel::m_Age << endl;
}

在这里插入图片描述
对于,羊和驼中都含有动物中年龄成员变量,羊驼中就会含有两份年龄成员变量,就会造成以哪个为准呢?
虽然我们可通过添加作用域进行区分,但仍会造成内存浪费,毕竟羊驼只需要一份就可。这个问题可以通过下面的方式进行解决:

优化示例:

虚继承:

  • 继承前加virtual关键字后,变为虚继承
  • 此时公共的父类Animal称为虚基类
class Animal
{
public:
	int m_Age;
};

class Sheep : virtual public Animal {};
class camel : virtual public Animal {};//camel-骆驼
class alpaca : public Sheep, public camel {};//alpaca-羊驼

void test01()
{
	alpaca st;
	st.Sheep::m_Age = 100;
	st.camel::m_Age = 200;

	cout << "st.Sheep::m_Age = " << st.Sheep::m_Age << endl;
	cout << "st.camel::m_Age = " << st.camel::m_Age << endl;
	cout << "st.m_Age = " << st.m_Age << endl;

	st.Sheep::m_Age = 300;
	cout << "st.Sheep::m_Age = " << st.Sheep::m_Age << endl;
	cout << "st.camel::m_Age = " << st.camel::m_Age << endl;
	cout << "st.m_Age = " << st.m_Age << endl;
}

在这里插入图片描述
可以看出,采用虚继承方式,羊驼继承动物的属性就变成了一份,无论改变羊和驼中哪个属性,三者中的属性变量就都改变了。
下面借助VS自带的开发人员命令提示符窗口,对上述内容进行一个深入介绍:

  • 普通示例:
    在这里插入图片描述
  • 优化示例:
    在这里插入图片描述
    对比两者,我们可以方向,参与虚继承方式下,动物类中属性(m_Age)只含有一份。
    虚继承下,羊和驼类中包含一个指针(vbptr-virtual base pointer虚基类指针)指向vbtable(虚基类表-图中1和2的位置),羊和驼中的虚基类表包含各自的偏移量,用于找到动物类中的属性(m_Age),羊和驼就不再分别基础动物类中的属性了,只继承一个指针用于找到动物类的属性,减少空间浪费。

总结:

  • 菱形继承带来的主要问题是子类继承两份相同的数据,导致资源浪费以及毫无意义
  • 利用虚继承可以解决菱形继承问题

7.多态

在编程语言和类型论中,多态(英语:polymorphism)指为不同数据类型的实体提供统一的接口
多态类型(英语:polymorphic type)可以将自身所支持的操作套用到其它类型的值上

7.1 多态的基本应用

7.1.1 多态的基本概念

多态是C++面向对象三大特性之一

多态分为两类:

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

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

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

非多态示例:

class Animal
{
public:
	void speak()
	{
		cout << "动物在说话" << endl;
	}
};

class Cat :public Animal
{
public:
	void speak()
	{
		cout << "小猫在说话" << endl;
	}
};

class Dog :public Animal
{
public:

	void speak()
	{
		cout << "小狗在说话" << endl;
	}

};

//希望实现每次调用函数,给什么形参就调用谁的函数
//提供一个公共接口,否则就需要多个函数才可实现对每个动物叫的实现
void DoSpeak(Animal& animal)
{
	animal.speak();
}

void test01()
{
	Cat cat;
	DoSpeak(cat);

	Dog dog;
	DoSpeak(dog);
}

在这里插入图片描述
但由输出来看与我们的想法不同,每次都调用动物类的说话函数(speak)。

原因:
由于doSpeak函数在编译阶段就确定了函数地址,即地址早绑定——静态多态

对于此种问题可以采用多态技术进行解决:

要实现调用谁,谁执行就需要使得doSpeak函数地址在运行阶段进行绑定,实现地址晚绑定——动态多态。

多态示例:
只需要在父类speak函数前添加关键字-virtual,形成虚函数。

class Animal
{
public:
	//Speak函数就是虚函数
	//函数前面加上virtual关键字,变成虚函数,那么编译器在编译的时候就不能确定函数调用了。
	virtual void speak()
	{
		cout << "动物在说话" << endl;
	}
};
//我们希望传入什么对象,那么就调用什么对象的函数
//如果函数地址在编译阶段就能确定,那么静态联编
//如果函数地址在运行阶段才能确定,就是动态联编

在这里插入图片描述

多态满足条件
1、有继承关系(继承是多态实现的基础);
2、子类重写父类中的虚函数:

  • 也就是与父类虚函数一样(关键字virtual可加可不加),返回值类型 函数名 形参列表完全一致

多态使用: 父类指针或引用指向子类对象,即:
void DoSpeak(Animal& animal)-父类引用;
void DoSpeak(Animal* animal)-父类指针。

7.1.2 多态的基本原理

class Animal
{
public:
	//Speak函数就是虚函数
	//函数前面加上virtual关键字,变成虚函数,那么编译器在编译的时候就不能确定函数调用了。
	virtual void speak()
	{
		cout << "动物在说话" << endl;
	}
};

class Cat :public Animal
{
public:
	void speak()
	{
		cout << "小猫在说话" << endl;
	}
};

class Dog :public Animal
{
public:

	void speak()
	{
		cout << "小狗在说话" << endl;
	}

};

//希望实现每次调用函数,给什么形参就调用谁的函数
//提供一个公共接口,否则就需要多个函数才可实现对每个动物叫的实现
void DoSpeak(Animal& animal)
{
	animal.speak();
}

void test01()
{
	Cat cat;
	DoSpeak(cat);
}

对于多态来说,需要满足两个条件,继承和重写虚函数。

  • 在子类未重写父类的虚函数时:
    在这里插入图片描述
    子类(猫类)由于继承父类,因此在子类未重写父类虚函数时,子类中将父类中的虚函数完全复制一份。
    (图中,父类存储一个指针(即vfptr),其指向vftable-虚函数表,表中放着虚函数的地址)
    下面借助VS自带的开发人员命令提示符窗口,对上述内容进行一个深入介绍:
    在这里插入图片描述
    在这里插入图片描述

  • 子类重写父类虚函数
    在这里插入图片描述
    子类虚函数表内部就替换成子类的(虚)函数地址,子类就有了自己的(虚)函数,从而可以进行调用自己的函数。
    下面借助VS自带的开发人员命令提示符窗口,对上述内容进行一个深入介绍:
    在这里插入图片描述

  • 父类指针或引用指向子类对象

void DoSpeak(Animal& animal)
{
	animal.speak();
}
//父类函数传入子类对象:DoSpeak(cat);
//Animal& animal=Cat;
//再进行调用父类函数:animal.speak();
//由于Animal& animal=Cat,指向子类对象(Cat),编译器就会在子类的虚函数表中去找内部的函数地址,从而调用子类函数。

7.2 纯虚函数和抽象类

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

纯虚函数语法:virtual 返回值类型 函数名 (参数列表)= 0 ;
当类中有了纯虚函数,这个类也称为抽象类

抽象类特点

  • 无法实例化对象;
  • 子类必须重写抽象类中的纯虚函数,否则也属于抽象类。
    示例:
class Base
{
public:
	//纯虚函数
	//类中只要有一个纯虚函数就称为抽象类
	virtual void func() = 0;
};

class Son :public Base
{
public:
	//子类必须重写父类中的纯虚函数,否则也属于抽象类
	virtual void func()
	{
		cout << "func调用" << endl;
	};
};

void test01()
{
	Base* base = NULL;
	//base = new Base; // 错误,抽象类无法实例化对象
	//父类指针指向子类对象
	base = new Son;
	base->func();//通过父类指针调用子类函数
	delete base;//记得销毁
}

7.3 虚析构和纯虚析构

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

class Animal {
public:

	Animal()
	{
		cout << "Animal 构造函数调用!" << endl;
	}
	virtual void Speak() = 0;

	~Animal()
	{
		cout << "Animal析构函数调用!" << endl;
	}
};

class Cat : public Animal {
public:
	Cat(string name)
	{
		cout << "Cat构造函数调用!" << endl;
		m_Name = new string(name);
	}
	virtual void Speak()
	{
		cout << *m_Name << "小猫在说话!" << endl;
	}
	~Cat()
	{
		cout << "Cat析构函数调用!" << endl;
		if (this->m_Name != NULL) {
			delete m_Name;
			m_Name = NULL;
		}
	}

public:
	string* m_Name;
};

void test01()
{
	Animal* animal = new Cat("Tom");//父类指针指向子类对象
	animal->Speak();
	delete animal;//释放
}

在这里插入图片描述

由此可知,对于子类在堆区开辟的空间,对父类指针释放未能对子类空间释放,造成内存泄漏。(未能调用子类析构函数)

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

  • 虚析构和纯虚析构共性

    • 可以解决父类指针释放子类对象;
    • 都需要有具体的函数实现
  • 虚析构和纯虚析构区别:

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

虚析构语法:

virtual ~类名(){}

纯虚析构语法:(类内声明,类外实现

类内声明: virtual ~类名() = 0;

类外实现:类名::~类名(){}
示例:

  • 虚析构函数
class Animal {
public:

	Animal()
	{
		cout << "Animal 构造函数调用!" << endl;
	}
	//纯虚函数
	virtual void Speak() = 0;

	法一:析构函数加上virtual关键字,变成虚析构函数
	virtual~Animal()
	{
		cout << "Animal虚析构函数调用!" << endl;
	}
};

在这里插入图片描述

  • 纯虚析构函数
class Animal {
public:

	Animal()
	{
		cout << "Animal 构造函数调用!" << endl;
	}
	//纯虚函数
	virtual void Speak() = 0;

	//法二:类内声明:纯虚析构函数
	//和包含普通纯虚函数的类一样,包含了纯虚析构函数的类也是一个抽象类。不能够被实例化。
	virtual ~Animal() = 0;
};

//法二:类外实现:
Animal::~Animal()
{
	cout << "Animal 纯虚析构函数调用!" << endl;
}

在这里插入图片描述
注意:纯虚析构函数不要忘了类外实现,否则会出现下面的错误;
在这里插入图片描述

总结:

​ 1. 虚析构或纯虚析构就是用来解决通过父类指针释放子类对象;

​ 2. 如果子类中没有堆区数据,可以不写为虚析构或纯虚析构;

​ 3. 拥有纯虚析构函数的类也属于抽象类。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

软件产品为什么要测试才能上线?测试可以发现所有bug吗?

在现如今信息时代&#xff0c;软件产品已经成为人们生活中不可或缺的一部分。无论是在工作中还是在娱乐休闲时&#xff0c;我们都需要依赖各种软件来完成各种任务。然而&#xff0c;你是否注意到了身边的软件产品都是经过严格的测试才能上线的呢?那么为什么软件产品必须要经过…

操作系统【OS】Ch2 进程同步与互斥机制

进程同步与互斥机制1&#xff1a;硬件和软件方法 硬件方法 也称为低级方法、元方法 不能实现让权等待 中断屏蔽 使用开/关中断指令实现简单高效只适用于单处理机只适用于操作系统内核进程【因为关中断的权力不能交给用户】 TestAndSet 实现简单适用于多处理机不满足让权等待…

【GAMES101】Lecture 09 重心坐标

我们之前说着色过程中以及这个计算法线的时候需要用到这个插值&#xff08;Interpolation&#xff09;&#xff0c;然后插值是通过这个重心坐标&#xff08;Barycentric Coordinates&#xff09;来实现的 目录 重心坐标 插值 重心坐标 注意哈我们这里说的三角形的重心坐标并…

RT-DETR优化改进:IoU系列篇 | Focaler-IoU​​​​​​​更加聚焦的IoU损失Focaler-IoU |2024年最新发表

🚀🚀🚀本文改进:Focaler-IoU更加聚焦的IoU损失Focaler-IoU,能够在不同的检测任务中聚焦不同的回归样本,使用线性区间映射的方法来重构IoU损失 🚀🚀🚀RT-DETR改进创新专栏:http://t.csdnimg.cn/vuQTz 🚀🚀🚀学姐带你学习YOLOv8,从入门到创新,轻轻松松搞…

(2024,强化学习,扩散,奖励函数)扩散模型的大规模强化学习

Large-scale Reinforcement Learning for Diffusion Models 公和众和号&#xff1a;EDPJ&#xff08;进 Q 交流群&#xff1a;922230617 或加 VX&#xff1a;CV_EDPJ 进 V 交流群&#xff09; 目录 0. 摘要 3. 方法 3.1. 使用多步骤 MDP 的策略梯度 3.2. 基于分布的奖励函…

正则表达式初版

一、简介 REGEXP&#xff1a; Regular Expressions&#xff0c;由一类特殊字符及文本字符所编写的模式&#xff0c;其中有些字符&#xff08;元字符&#xff09;不表示字符字面意义&#xff0c;而表示控制或通配的功能&#xff0c;类似于增强版的通配符功能&#xff0c;但与通…

Docker部署

Docker简介 Docker是一个开源的容器引擎&#xff0c;它有助于更快地交付应用。 Docker可将应用程序和基础设施层隔离&#xff0c;并且能将基础设施当作程序一样进行管理。使用 Docker可更快地打包、测试以及部署应用程序&#xff0c;并可以缩短从编写到部署运行代码的周期。 &a…

数据结构奇妙旅程之二叉树题型解法总结

꒰˃͈꒵˂͈꒱ write in front ꒰˃͈꒵˂͈꒱ ʕ̯•͡˔•̯᷅ʔ大家好&#xff0c;我是xiaoxie.希望你看完之后,有不足之处请多多谅解&#xff0c;让我们一起共同进步૮₍❀ᴗ͈ . ᴗ͈ აxiaoxieʕ̯•͡˔•̯᷅ʔ—CSDN博客 本文由xiaoxieʕ̯•͡˔•̯᷅ʔ 原创 CSDN …

影响可变利差有几个因素,Anzo Capital先说两个

了解利差的变化规律&#xff0c;盈利赚钱还不是轻轻松松的事情&#xff0c;但Anzo Capital想问各位投资者&#xff0c;你们知道影响可变利差的价值有几个因素吗&#xff1f;今天就先抛砖引玉&#xff0c;先说两个影响可变利差的因素。 首先就是交易工具的流动性——商品快速买…

精通 VS 调试技巧,学习与工作效率翻倍!

​ ✨✨ 欢迎大家来到贝蒂大讲堂✨✨ ​ &#x1f388;&#x1f388;养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; ​ 所属专栏&#xff1a;C语言学习 ​ 贝蒂的主页&#xff1a;Betty‘s blog 1. 什么是调试 当我们写代码时候常常会遇见输出结果不符合我们预…

【三维重建】双目立体视觉

通过极几何可以求得极线&#xff0c;现在我们需要将左边的图变成右边的平行视图。 所有的极线都经过极点(e/e)&#xff0c;如果极点位于无穷远处&#xff0c;那所有的极线都平行。 (极几何的基础知识可以参考这篇文章&#xff1a;【三维重建】对极几何-CSDN博客) 平行视图中&…

modbus poll测试工具测试modbus tcp与PLC设备连接使用方法

socket默认端口是502&#xff0c;socket连上之后&#xff0c; 按照modbuspoll工具设置的读写参数 生成的RTU命令格式去组装读PLC的设备数据 modbuspoll工具配置&#xff0c;以v9.9.2中文破解版为例&#xff1a; 首先点连接菜单&#xff08;connection&#xff09;建立连接&…

基于springboot+vue的IT技术交流和分享平台系统(前后端分离)

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战 主要内容&#xff1a;毕业设计(Javaweb项目|小程序等)、简历模板、学习资料、面试题库、技术咨询 文末联系获取 研究背景…

【latex】在Overleaf的IEEE会议模板中,快速插入参考文献

【LaTeX】在Overleaf的IEEE会议模板中&#xff0c;快速插入参考文献 写在最前面第一步&#xff1a;在文献检索网站导出引用文献的bib文件第二步&#xff1a;编辑overleaf模版方法二&#xff1a;EduBirdie生成参考文献&#xff08;补充&#xff09;使用LaTeX在Overleaf的IEEE会议…

《Linux高性能服务器编程》笔记07

Linux高性能服务器编程 本文是读书笔记&#xff0c;如有侵权&#xff0c;请联系删除。 参考 Linux高性能服务器编程源码: https://github.com/raichen/LinuxServerCodes 豆瓣: Linux高性能服务器编程 文章目录 Linux高性能服务器编程第14章 多线程编程14.1 Linux线程概述14…

汇编实验报告

汇编实验 实验4 分支程序设计实验5 循环程序设计 实验4 分支程序设计 一、实验目的 理解分支程序结构的特点&#xff0c;掌握分支结构程序的编写。 二、实验内容 &#xff08;1&#xff09;验证单分支结构的字母判断程序&#xff08;教材例4-10&#xff09;&#xff0c;编写数…

基于蒙特卡洛模拟的家用电动汽车充电负荷预测(MATLAB实现)

采用蒙特卡洛模拟法&#xff0c;对家用电动汽车充电负荷进行预测&#xff0c;电动汽车分为快、中、慢三种充电功率&#xff0c;且分为一天一充、一天两充、一天三充三种类型。全部MATLAB代码在下方给出&#xff0c;可以直接运行。 %%%%%%%%%%%%%%%%%%%%%%%%输入电动汽车相关原…

前端开发WebStorm

WebStorm是一款功能强大的JavaScript集成开发环境&#xff0c;凭借智能代码补全、实时分析和代码重构、集成版本控制、强大的调试和测试工具、实时预览和集成前端工具以及自定义配置和插件支持等功能&#xff0c;成为开发者首选的利器。 前端开发WebStorm WebStorm是一款功能强…

使用POI生成word文档的table表格

文章目录 使用POI生成word文档的table表格1. 引入maven依赖2. 生成table的两种方式介绍2.1 生成一行一列的table2.2 生成固定行列的table2.3 table合并列2.4 创建多个table存在的问题 使用POI生成word文档的table表格 1. 引入maven依赖 <dependency><groupId>org.…

【QT】MDI应用程序设计

目录 1 MDI简介 2 文档窗口类QFormDoc的设计 3 MDI主窗口设计与子窗口的使用 3.1 主窗口界面设计 3.2 MDI子窗口的创建与加入 3.3 QMdiArea常用功能函数 3.4 MDI的信号 1 MDI简介 传统的应用程序设计中有多文档界面&#xff08;Multi-documentInterface&#xff0c;MDI…