【C++】—— 继承(上)

news2024/11/26 0:12:30

【C++】—— 继承(上)

  • 1 继承的概念与定义
    • 1.1 继承的概念
    • 1.2 继承定义
      • 1.2.1 定义格式
      • 1.2.2 继承父类成员访问方式的变化
    • 1.3 继承类模板
  • 2 父类和子类对象赋值兼容转换
  • 3 继承中的作用域
    • 3.1 隐藏规则
    • 3.2 例题
  • 4 子类的默认成员函数
    • 4.1 构造函数
      • 4.1.1 父类有默认构造
      • 4.1.2 父类没有默认构造
    • 4.2 拷贝构造
      • 4.2.1 不需要自己显式写
      • 4.2.2 自己显式写
    • 4.3 赋值重载
    • 4.4 析构函数
      • 4.4.1 重载
      • 4.4.2 顺序
    • 4.5 实现不能被继承的类
      • 4.5.1 法一:设为私有
      • 4.5.2 法二:final
    • 4.6 总结

1 继承的概念与定义

1.1 继承的概念

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

  下面我们通过一个例子来初步感受一下继承:

class Student
{
	public :
	// 进⼊校园/图书馆/实验室刷⼆维码等⾝份认证
	void identity()
	{
		// ...
	} 
	// 学习
	void study()
	{
		// ...
	}
protected:
	string _name = "peter"; // 姓名
	string _address; // 地址
	string _tel; // 电话
	int _age = 18; // 年龄
	int _stuid; // 学号
};

class Teacher
{
public:
	// 进⼊校园/图书馆/实验室刷⼆维码等⾝份认证
	void identity()
	{
		// ...
	} 
	// 授课
	void teaching()
	{
		//...
	}
protected:
	string _name = "张三"; // 姓名
	int _age = 18; // 年龄
	string _address; // 地址
	string _tel; // 电话
	string _title; // 职称
};

  上面,我们看到没有继承之前我们设计了两个类 StudentTeacher S t u d e n t Student Student T e a c h e r Teacher Teacher 都有 姓名 / 地址 / 电话 / 年龄 等成员变量,都有 i d e n t i t y identity identity ⾝份认证的成员函数,设计到两个类里面就是冗余的。当然他们也有⼀些独有的成员变量和函数,比如老师独有成员变量是职称,学生的独有成员变量是学号;学⽣的独有成员函数是学习,⽼师的独有成员函数是授课。

  既然 S t u d e n t Student Student T e a c h e r Teacher Teacher 两个类的设计有些冗余,那我们能不能把公共的信息提取出来呢?

  下面我们公共的成员都放到 Person 中,Student 和 Teacher 都继承 Person,就可以复⽤这些成员,就不需要重复定义了,省去了很多麻烦。

class Person
{
public:
	// 进⼊校园/图书馆/实验室刷⼆维码等⾝份认证
	void identity()
	{
		cout << "void identity()" << _name << endl;
	}
protected:
	string _name = "张三"; // 姓名
	string _address; // 地址
	string _tel; // 电话
	int _age = 18; // 年龄
};


class Studen : public Person
{
public:
	// 学习
	void study()
	{
		// ...
	}
protected:
	int _stuid; // 学号
};

class Teacher : public Person
{
public:
	// 授课
	void teaching()
	{
		//...
	}
protected:
	string title; //职称
};

  虽然 S t u d e n t Student Student类 的成员变量看起来只有int _stuid;,但它继承了Person类,它还有string _namestring _address;等等成员变量。成员函数也不止void study(),还有void identity()
  
  

1.2 继承定义

1.2.1 定义格式

  下面我们看到 Person父类,也称作基类Student子类,也称作派生类。(因为翻译的原因,所以既叫父类/子类,也叫基类/派生类)

在这里插入图片描述

  
  继承方式与访问限定符一样,都有三个公有、保护、私有

在这里插入图片描述

  

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

类成员/继承方式 p u b l i c public public 继承 p r o t e c t e d protected protected 继承 p r e v a t e prevate prevate 继承
基类的 p u b l i c public public 成员派生类的public成员派生类的 p r o t e c t e d protected protected 成员派生类的 p r i v a t e private private 成员
基类的 p r o t e c t e d protected protected 成员派生类的 p r o t e c t e d protected protected 成员派生类的 p r o t e c t e d protected protected 成员派生类的 p r i v a t e private private 成员
基类的 p r i v a t e private private 成员在派生类中不可见在派生类中不可见在派生类中不可见
  • 父类的 private成员 在子类中无论以什么方式继承都是不可见的。这里的不可见是指父类的私有成员还是被继承到了子类对象中,但是语法上限制子类对象不管在类里面还是类外我们都不能去访问它

    • 子类想访问父类的 p r i v a t e private private 成员虽然不能直接访问,但能间接访问。虽然在子类中是不能访问,但在父类中并没有相关限制,只用父类提供相关访问 p r i v a t e private private 成员变量的成员函数,子类调用其函数就能间接访问。
  • 父类 p r i v a t e private private 成员在子类中是不能被访问的,如果父类成员不想在类外直接被访问,但需要在子类中能访问,就定义为 protected。可以看出保护成员限定符是因继承才出现的。

  • 实际上面的表格我们进行一下总结会发现,父类的私有成员在子类都是不可见。父类其他成员在子类的访问方式为: M i n Min Min(成员在父类的访问限定符, 继承方式) p u b l i c public public >  p r o t e c t e d protected protected >  p r i v a t e private private

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

    • class Student:Person //默认为private继承struct Student:Person //默认为public继承
  • 在实际运用中一般使用的都是 p u b l i c public public 继承,几乎很少使用 p r o t e c t e d protected protected / p r i v a t e private private 继承,也不提倡使用 p r o t e c t e d protected protected / p r i v a t e private private 继承,因为 p r o t e c t e d protected protected / p r i v a t e private private 继承下来的成员都只能在子类的类里面使用,实际中扩展维护性不强。这里可以认为是 C++ 过度设计了。

  看起来上面的规则很复杂,实际实践过程中是很简单的,一般都是:父类我们就用公有和保护,继承方式我们就用公有。其他方式都很少使用。
  

1.3 继承类模板

  上述都是一些普通类的继承,那如果我们想继承类模板又该怎样呢?

  之前,我们模拟实现栈使用的适配器模式,其实还有一种方法:继承

namespace ganyu
{
	template<class T>
	class stack : public std::vector<T>
	{
	public:
		void push(const T& x)
		{
			push_back(x);
		}
		void pop()
		{
			vector<T>::pop_back();
		}
		const T& top()
		{
			return vector<T>::back();
		}
		bool empty()
		{
			return vector<T>::empty();
		}
	};
}

  当基类是类模板时,需要指定类域去访问,否则会编译报错。(普通类的继承不存在这个问题)

int main()
{
	ganyu::stack<int> st;
	st.push(1);
	st.push(2);
	st.push(3);
	while (!st.empty())
	{
		cout << st.top() << " ";
		st.pop();
	}

	return 0;
}

在这里插入图片描述

  为什么编译报错呢?这与按需实例化有关系

  ganyu::stack<int> st;这句代码实例化栈,将 T T T 实例化成 i n t int int,也间接将 v e c t o r vector vector 实例化(严格来说只实例化了栈的构造函数)。但我们将 v e c t o r vector vector 实例化时不会把 v e c t o r vector vector 中所有的成员函数都实例化,我们调用谁才实例化谁
  我们调用 p u s h push push 函数时,编译器去找 p u s h push push_ b a c k back back 函数,在子类和父类中都找不到,因为还没有实例化。所以我们要指定类域去访问,表示调用的是 v e c t o r vector vector< T T T> 中的 p u s h push push_ b a c k back back,此时编译器看到 T T T 已经被实例化成 i n t int int 了,就会将 v e c t o r vector vector< T T T> 中的 p u s h push push_ b a c k back back 实例化出一份 i n t int int 版本的出来。

  我们可以结合 #define,能灵活更改 s t a c k stack stack 的底层容器,达到类似适配器模式的效果

#define CONTAINER vector

namespace ganyu
{
	template<class T>
	class stack : public std::CONTAINER<T>
	{
	public:
		void push(const T& x)
		{
			CONTAINER<T>::push_back(x);
		}
		void pop()
		{
			CONTAINER<T>::pop_back();
		}
		const T& top()
		{
			return CONTAINER<T>::back();
		}
		bool empty()
		{
			return CONTAINER<T>::empty();
		}
	};
}

  
  

2 父类和子类对象赋值兼容转换

  • p u b l i c public public继承的前提下,子类对象可以赋值给父类的对象 / 父类的指针 / 父类的引用。这里有个形象的说法叫切片或者切割。寓意把子类中父类那部分切割开来赋值给父类对象/指针/引用
  • 但反过来就不成立:父类对象不能赋值给子类对象

在这里插入图片描述

  例如:现在有一个 S t u d e n t Student Student 对象, S t u d e n t Student Student 对象可以赋值给父类对象 P e r s o n Person Person,当然,指针和引用也是可以的;但反过来就不成立(总不能无中生有出一个 _ N o No No 成员吧)。

class Person
{
protected :
	string _name; // 姓名
	string _sex; // 性别
	int _age; // 年龄
};

class Student : public Person
{
public :
	int _No; // 学号
};

int main()
{
	Student sobj;

	// 1.派⽣类对象可以赋值给基类的对象/指针/引⽤
	Person pobj = sobj;
	Person* pp = &sobj;
	Person& rp = sobj;
	
	//2.基类对象不能赋值给派⽣类对象,这⾥会编译报错
	//sobj = pobj;

	return 0;
}

  这里并没有发生类型转换
  虽然我们前面讲过不同类型的对象之间进行赋值,支持的是类型转换

int i = 0;
double d = i;

  将 i i i 赋值给 d d d 走的就是类型转换,中间会生成一个临时对象
  但是切片并不是类型转换,中间并没有产生临时变量,这是一种特殊处理。

  • 基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。但是必须是基类的指针是指向派生类对象时才是安全的。这里基类如果是多态类型,可以使用RTTI(Run-Time Type Information)的 dynamic_cast 来进行识别后进行安全转换。(ps:这个我们后面再单独专门介绍,这里先提⼀下)
      
      

3 继承中的作用域

3.1 隐藏规则

  • 在继承体系中父类和子类都有独立的作用域
  • 子类和父类中有同名成员子类成员屏蔽父类的同名成员的直接访问,这种情况叫 隐藏。(在子类成员函数中,可以使用父类::父类成员 显式访问
class Person
{
protected :
	string _name = "小帅"; // 姓名
	int _num = 111; // ⾝份证号
};
class Student : public Person
{
public :
	void Print()
	{
		cout << " 姓名:" << _name << endl;
		cout << " 身份证号:" << Person::_num << endl;//指定父类的类域进行访问
		cout << " 学号:" << _num << endl;
	}
protected:
	int _num = 999; // 学号
};

int main()
{
	Student s1;
	s1.Print();
	return 0;
}

运行结果:

在这里插入图片描述

  • 如果是成员函数的隐藏只需要函数名相同就构成隐藏
  • 注意:在实际中在继承体系里面最好不要定义重名的成员或函数

  
  

3.2 例题

class A
{
	public :
	void fun()
	{
		cout << "func()" << endl;
	}
};
class B : public A
{
	public :
	void fun(int i)
	{
		cout << "func(int i)" << i << endl;
	}
};
int main()
{
	B b;
	b.fun(10);
	b.fun();
	return 0;
};
  1. A A A B B B 类中的两个 f u n c func func函数 构成什么关系()
    A. 重载   B. 隐藏  C.没关系

  2. 下面程序的编译运行结果是什么()
    A. 编译报错  B. 运行报错  C. 正常运行

  • 第一题:第一眼看上去,他们构成重载关系:函数名相同,参数类型不同。但如果选 A 就错了,这题选B。别忘了,只有在同一作用域的函数才构成函数重载,而隐藏是父类和子类中的函数名相同就构成隐藏
  • 第二题:选A,因为子类和父类的 f u n c func func函数 构成隐藏,除非指定父类的作用域去调用,否则同名成员或函数是不会去父类中查找的。b.fun(); 没有传递参数,编译报错。

  
  

4 子类的默认成员函数

  6 个默认成员函数,意思是我们不写,编译器会给我们自动生成。父类的默认成员函数与普通类没有任何差别,但在派生类中,这几个成员函数是如何生成的呢?

4.1 构造函数

  • 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显式调用。

4.1.1 父类有默认构造

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

protected:
	string _name; // 姓名
};

class Student : public Person
{
public:
	
protected:
	int _num; //学号
	string _sex; //性别
};

  

首先,我们来回忆一下普通类的默认生成的构造函数的行为:

  • 内置类型:默认生成的构造函数是不确定的
  • 自定义类型:会调用它的默认构造函数

现在,比起之前多出来一部分:父类成员

  • 我们把继承的父类成员看成一个整体对象,子类的默认构造会自动调用父类的默认构造完成父类成员的初始化

  

在这里插入图片描述

  

4.1.2 父类没有默认构造

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

protected:
	string _name; // 姓名
	double _height; //身高
};

class Student : public Person
{
public:
	
protected:
	int _num; //学号
	string _sex; //性别
};

  现在,父类没有默认构造,派生类还能默认生成构造函数吗?.

在这里插入图片描述

  可见,默认生成的只能调用默认构造。这时,就需要我们在子类显式写一个构造函数了

Student(const char* name, double height, int num, const char* sex)
	:_name(name)
	,_height(height)
	,_num(num)
	,_sex(sex)
{}

  这样写可不可以呢?
  不可以。编译器不允许直接去初始化父类的成员,子类要求必须调用父类的构造函数来初始化父类的成员,要把父类当成一个整体。
  
显示调用父类方法如下:

Student(const char* name, double height, int num, const char* sex)
	:Person(name, height)
	,_num(num)
	,_sex(sex)
{}

int main()
{
	Student s1("张三", 1.80, 1, "男");
	Student s2("李四", 1.70, 2, "未知");

	return 0;
}

  有点像调用一个匿名对象一样。

  

4.2 拷贝构造

  派生类的拷贝构造函数必须调用基类的拷贝构造完成基本的拷贝初始化
  

对默认生成的拷贝构造,其行为也像上述构造函数一样分成三类

  • 内置类型:完成浅拷贝
  • 自定义类型:调用其拷贝构造
  • 父类整体:调用父类的拷贝构造

  

4.2.1 不需要自己显式写

class Person
{
public:
	Person(const char* name = "peter")
		: _name(name)
	{
		cout << "Person()" << endl;
	}
	Person(const Person& p)
		: _name(p._name)
	{
		cout << "Person(const Person& p)" << endl;
	}

protected:
	string _name; // 姓名
};

class Student : public Person
{
public:
	//构造函数
	Student(const char* name, int num, const char* sex)
		:Person(name)
		, _num(num)
		, _sex(sex)
	{}
	
	//未写拷贝构造
	//···
	
protected:
	int _num; //学号
	string _sex; //性别
};

int main()
{
	Student s1("张三", 1, "男");
	Student s2 = s1;
	
	return 0;
}

在这里插入图片描述

  严格来说, S t u d e n t Student Student类是不用我们自己写拷贝构造的,默认生成的拷贝构造已经完成了我们的需求。前面,我们通过学习知道拷贝构造、赋值重载、析构是一体的。一个不需要写,三个都不需要写;一个要写,三个都要写。因此 S t u d e n t Student Student类 的赋值重载和析构函数都不需要自己写
  如果有需要深拷贝的资源,才需要自己实现

  

4.2.2 自己显式写

  那假设 S t u d e n t Student Student 类中有指向的资源,需要我们自己写拷贝构造,又该怎么写呢?

class Student : public Person
{
public:

protected:
	int _num; //学号
	string _sex; //性别

	int* _ptr = new int[10];//假设有指向的资源
};
Student (const Student& s)
	:_num(s._num)
	,_sex(s._sex)
	,//显示调用父类的拷贝构造
{
	//深拷贝
	memcpy(_ptr, s._ptr, sizeof(int) * 10);
}

  如何显式调用父类的拷贝构造呢?

  调用父类的拷贝构造,需要传递父类的对象,但现在没有父类的对象,咋办呢?
  这时,我们就可以运用前面学习的赋值兼容转换

Student(const Student& s)
	:_num(s._num)
	,_sex(s._sex)
	,Person(s)
{
	//深拷贝
	memcpy(_ptr, s._ptr, sizeof(int) * 10);
}

  Person(s) s s s 是子类对象的引用,要拷贝父类那一部分,需要将父类那一部分拿出来, 怎么拿出来呢?我把子类对象传给父类的引用,这时父类的引用,引用的是子类对象中切割出来的父类的那一部分

  这里有个小细节,走初始化列表时,编译器会先走Person(s),在走_num(s._num)_sex(s._sex)
  这是因为初始化列表初始化的顺序与成员在列表中的顺序无关,只与声明的顺序有关
  所以继承以后,它将父类对象当成一个整体,而父类对象是最先被声明

  那如果不在初始化列表显示初始化父类呢?

Student(const Student& s)
	:_num(s._num)
	, _sex(s._sex)	
{
	//深拷贝
}

  我们说过,所有成员都会走初始化列表,父类 P e r s o n Person Person 没有显示调用,也会走初始化列表。但此时编译器会调用 P e r s o n Person Person 的默认构造,虽然编译能通过,但很可能不符合你的需求;如果 P e r s o n Person Person 没有默认构造,那么编译报错

  

4.3 赋值重载

  和拷贝构造一样, S t u d e n t Student Student 类严格来说不需要写赋值。
  但如果我们需要显式写要怎么写呢

  • 派生类的 o p e r a t o r operator operator= 必须要调用基类的 o p e r a t o r operator operator =
Student& operator=(const Student& s)
{
	if (this != &s)
	{
		operator=(s);
		_num = s._num;
		_sex = s._sex;
	}
	return *this;
}

  复制拷贝与拷贝构造是类似的,都是传递子类对象的引用给父类即可。

  但是,如果运行程序会发现:程序陷入死循环
  为什么呢?
  子类中的同名函数与父类的构成了隐藏!
  operator=(s);其实一直调的是子类的 o p e r a t o r operator operator=,因此程序陷入死循环

  因此我们要指定调用定父类的 o p e r a t o r operator operator=。

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

  总结:派生类的 o p e r a t o r operator operator= 必须要调用基类的 o p e r a t o r operator operator= 完成基类的赋值。需要注意的是派生类的 o p e r a t o r operator operator= 屏蔽了基类的 o p e r a t o r operator operator=,所以显式调用基类的 operator=,需要指定基类作用域

  

4.4 析构函数

  首先,严格来说 S t u d e n t Student Student 并不需要我们显式写析构函数
  那如果有需要显式释放的资源,析构函数又该怎么写呢?

4.4.1 重载

  我们还是以 S t u d e n t Student Student类 为例
  首先,如果显式实现析构函数,_ n u m num num 和 _ s e x sex sex 是不用管的。因为int _num是内置类型,而 string _sex会自己调用其析构。我们只需要管父类即可

~Student()
{
	~Person();
}

  但这样会报错

在这里插入图片描述

  析构是可以显示调用的,但为什么这里调不动呢?

  这里有个小知识点:子类的析构会和父类的析构构成隐藏关系
  因为一些特殊的原因,析构函数的函数名会被特殊处理成 d e s t r u c t o r destructor destructor(),所以父类的析构函数和子类的析构函数构成隐藏关系。实际上并没有什么 ~ S t u d e n t Student Student() 和 ~ P e r s o n Person Person(),只有 d e s t r u c t o r destructor destructor()。

  所以我们要指定类域调用

~Student()
{
	Person::~Person();
}

  

4.4.2 顺序

  我们来尝试调用一下析构函数

class Person
{
public:
	//成员函数
	//···
	
	~Person()
	{
		cout << "~Person()" << endl;
	}

protected:
	string _name; // 姓名
};

class Student : public Person
{
public:
	//成员函数
	//···

	~Student()
	{
		Person::~Person();
	}

protected:
	int _num; //学号
	string _sex; //性别
};

int main()
{
	Student s1("张三", 1, "男");
	Student s2("李四", 2, "未知");

	return 0;
}

运行结果:

在这里插入图片描述

  大家有没有发现析构函数调的有点多啊,我一个就两个对象,你怎么就调用 4 次析构函数了呢?

  像构造、赋值重载等,我们显式写的都需要显式调用父类的对应函数,但析构不需要显式调用。调用了子类析构函数之后,系统会自动调用父类的析构(这点与自定义类型的成员很像)。

  为什么要这样的。这样可以保证析构顺序是先子后父。后定义的先析构,而对象构造时,是先构造(初始化)父类,在初始化子类;析构是就需要先析构子类,在析构父类。如果显式调用就不能保证先子后父,而是取决于实现的人。
  
  

4.5 实现不能被继承的类

  要实现一个不能被继承的类,有两种方法

4.5.1 法一:设为私有

  将父类的构造函数设置为私有

class Base 
{
public :
	void func5() { cout << "Base::func5" << endl; }
protected:
	int a = 1;
private:
	// C++98的⽅法
	Base()
	{}
};

class Derive :public Base
{
	void func4() { cout << "Derive::func4" << endl; }
	
protected:
	int b = 2;
};

  为什么呢?因为子类的构造函数,不论是自动生成还是我们自己显式实现,都必须调用父类的构造函数。但是父类的 p r i v a t e private private成员在子类中是不可见的,因此子类调不到父类的构造函数

  但是这种方式不够明显,如果不调用子类的对象编译器是不会报错的

4.5.2 法二:final

  C++11中新增了一个关键字: f i n a l final final
  用 f i n a l final final 修饰一个类,表示该类是最终类,无法再被继承

  这种方式更直观一些,不管子类定不定义,直接报错

class Base final
{
public :
	Base()
	{}

	void func5() { cout << "Base::func5" << endl; }
protected:
	int a = 1;
};

class Derive :public Base
{
	void func4() { cout << "Derive::func4" << endl; }
protected:
	int b = 2;
};

在这里插入图片描述

  
  

4.6 总结

  • 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表显示调用
  • 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化
  • 派生类的 o p e r a t o r operator operator= 必须要调用基类的 o p e r a t o r operator operator= 完成基类的复制。需要注意的是派生类的 o p e r a t o r operator operator= 隐藏了基类的 o p e r a t o r operator operator=,所以显示调用基类的 operator=,需要指定基类作用域
  • 派生类的析构函数会在被调用完成之后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类对象先清理派生类成员在清理基类成员的顺序
  • 派生类对象初始化先调用基类的构造再调派生类的构造
  • 派生类对象析构清理先调用派生类析构再调基类的析构
  • 因为多态中一些场景析构函数需要构成重写,重写的条件之一是函数名相同。那么编译器会对析构函数进行特殊处理 ,处理成 d e s t r u c t o r destructor destructor(),所以基类析构函数不加 virtual 的情况下,派生类析构函数和基类析构函数构成隐藏关系
  • 大多数情况下,派生类中拷贝构造、赋值、析构都是不需要自己写的;如果需要,那这个继承的设计太过复杂,可以考虑重新设计。

  
  
  
  
  


  好啦,本期关于继承的知识就介绍到这里啦,希望本期博客能对你有所帮助。同时,如果有错误的地方请多多指正,让我们在 C++ 的学习路上一起进步!

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

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

相关文章

稀缺是否意味着价值

省流版&#xff1a;物以稀为贵。 稀少并不等于需求。 更新为&#xff1a;物以希为贵。 有需求就意味着有价值。 不管是20&#xff1a;80中的20&#xff0c;还是10&#xff1a;90中的10&#xff0c;还是2&#xff1a;98中的2。 所以&#xff0c;这个模型里一定会出现1这类人&a…

MambaAD 实验部分讲解

4 实验 4.1 设置&#xff1a;数据集、指标和细节 数据集&#xff08;6个&#xff09; 1.MVTec-AD&#xff1a; 包含5种类型的纹理和10种类型的对象&#xff0c;总共5,354张高分辨率图像。 实验&#xff1a; 3,629张正常图像被指定为训练。 剩下的 1,725 张图像被保留用于测试…

AWS MySQL 升级(三)—— TAZ - 近0停机的小版本升级方案

与AWS交流了解到的新方案&#xff0c;没有实际试过&#xff0c;所以本篇主要是些原理 一、 TAZ的含义 TAZ实际上就是 3 AZ&#xff0c;扩展一些就是 Multi-AZ DB Cluster&#xff0c;即在3个可用区部署DB&#xff0c;具备两个只读备用实例。 二、 TAZ的主要用途 1. 近0停机的小…

C++之模版进阶篇

目录 前言 1.非类型模版参数 2.模版的特化 2.1概念 2.2函数模版特化 2.3 类模板特化 2.3.1 全特化和偏特化 2.3.2类模版特化应用实例 3.模版分离编译 3.1 什么是分离编译 3.2 模板的分离编译 3.3 解决方法 4. 模板总结 结束语 前言 在模版初阶我们学习了函数模版和类…

单细胞组学大模型(6)--- LangCell,医学/细胞文本知识增强模型效果

–https://arxiv.org/abs/2405.06708 代码开源&#xff1a;https://github.com/PharMolix/OpenBioMed LangCell: Language-Cell Pre-training for Cell Identity Understanding 留意更多内容&#xff0c;欢迎关注微信公众号&#xff1a;组学之心 研究团队和研究单位 聂再清…

Python画笔案例-077 绘制 颜色饱和度测试

1、绘制 颜色饱和度测试 通过 python 的turtle 库绘制 颜色饱和度测试,如下图: 2、实现代码 绘制 颜色饱和度测试,以下为实现代码: """饱和度渐变示例,本程序需要coloradd模块支持,请在cmd窗口,即命令提示符下输入pip install coloradd进行安装。本程序演…

如何彻底掌握 JavaScript 设计模式 23 大核心模式助你提升编程水平

如何彻底掌握 JavaScript 设计模式 23 大核心模式助你提升编程水平 设计模式是解决特定问题的常用解决方案&#xff0c;它们可以帮助开发者编写更清晰、可维护、可扩展的代码。在 JavaScript 中&#xff0c;常见的设计模式可以分为三大类&#xff1a;创建型模式、结构型模式 和…

javaweb - 请求响应02

数组集合参数 数组参数&#xff1a;请求参数名与形参数组名称相同且请求参数为多个&#xff0c;定义数组类型形参即可接收参数。 RequestMapping("/arrayParam")public String arrayParam(String[] hobby) {for (String s : hobby) {System.out.println(s);}retu…

xtu oj 神经网络

回顾 Dedicated to you. AB III问题 H: 三角数问题 G: 3个数等式 数组下标查询&#xff0c;降低时间复杂度1405 问题 E: 世界杯xtu 数码串 题目 某神经网络模型是如下 1.一共有m层&#xff0c;每层都有几个神经元&#xff0c;从上到下编号为1到n。 2.第 i 层的神经元只有第 i …

基于Python的自然语言处理系列(26):Get to the Point Summarization

在本篇文章中,我们将实现经典的"Get to the Point"模型,该模型最初发表于 Get to the Point: Summarization with Pointer-Generator Networks。这是当时最著名的摘要生成模型之一,至今仍有很多人使用其Pointer-Generator架构作为他们模型的一部分。 1. 模型简介…

数据库管理-第248期 23ai:全球分布式数据库-分片数据分布方法(20241006)

数据库管理248期 2024-10-06 数据库管理-第248期 23ai&#xff1a;全球分布式数据库-分片数据分布方法&#xff08;20241006&#xff09;1 系统管理分片2 用户定义分片2.1 分片空间2.2 在用户定义分片配置中添加分片空间2.3 为用户定义分片创建表空间2.4 用户定义分片创建分片表…

AI大模型应用开发实战-AI时代应用开发破局!

后端应用级开发者该如何拥抱 AI GC&#xff1f;就是在这样的一个大的浪潮下&#xff0c;我们的传统的应用级开发者。我们该如何选择职业或者是如何去快速转型&#xff0c;跟上这样的一个行业的一个浪潮? 0 AI金字塔模型 越往上它的整个难度就是职业机会也好&#xff0c;或者说…

毒蘑菇检测数据集 9200张 14类毒蘑菇 带标注 voc yolo

毒蘑菇检测数据集 9200张 14类毒蘑菇 带标注 voc yolo 分类名: (图片张数,标注个数) Amanita citrina: (700, 816) Gyromitra infula: (842, 1102) Hygrophoropsis aurantiaca: (766, 1578) Imleria badia: (794, 1027) Lactarius turpis: (728, 891) Boletus reticulatus: (67…

微信小程序开发-配置文件详解

文章目录 一&#xff0c;小程序创建的配置文件介绍二&#xff0c;配置文件-全局配置-pages 配置作用&#xff1a;注意事项&#xff1a;示例&#xff1a; 三&#xff0c;配置文件-全局配置-window 配置示例&#xff1a; 四&#xff0c;配置文件-全局配置-tabbar 配置核心作用&am…

日期类(Date)的实现 (C++版)

​ &#x1f339;个人主页&#x1f339;&#xff1a;喜欢草莓熊的bear &#x1f339;专栏&#x1f339;&#xff1a;C入门 目录 前言 一、Date的头文件&#xff0c;包含函数声明 二、 Date.cpp 2.1 int GetMonthDay(int year, int month) 2.2 bool Check() 2.3 Date& …

基于YOLOv8-deepsort算法的智能车辆目标检测车辆跟踪和车辆计数

关于深度实战社区 我们是一个深度学习领域的独立工作室。团队成员有&#xff1a;中科大硕士、纽约大学硕士、浙江大学硕士、华东理工博士等&#xff0c;曾在腾讯、百度、德勤等担任算法工程师/产品经理。全网20多万粉丝&#xff0c;拥有2篇国家级人工智能发明专利。 社区特色…

HTB:Funnel[WriteUP]

目录 连接至HTB服务器并启动靶机 1.How many TCP ports are open? 2.What is the name of the directory that is available on the FTP server? 3.What is the default account password that every new member on the "Funnel" team should change as soon a…

cudnn8编译caffe过程(保姆级图文全过程,涵盖各种报错及解决办法)

众所周知,caffe是个较老的框架,而且只支持到cudnn7,但是笔者在复现ds-slam过程中又必须编译caffe,我的cuda版本是11.4,最低只支持到8.2.4,故没办法,只能编译了 在此记录过程、报错及解决办法如下; 首先安装依赖: sudo apt-get install git sudo apt-get install lib…

李宏毅 X 苹果书 自注意力机制 学习笔记下

b1 &#xff0c;b2...不是依序产生&#xff0c;而是同时被计算好的 从矩阵乘法角度看待self-attention运作过程 矩阵运算表示每一个a都要产生 a k v的操作如下&#xff1a; 矩阵运算表示的计算如下&#xff1a; A‘是A的normalization &#xff0c;用softmax 矩阵运算表示b计…

Ubuntu有关redis的命令

防火墙&#xff1a; systemctl status firewalld systemctl stop firewalld systemctl disable firewalld.service ifconfig查看ip地址 redis.conf在/etc/redis下&#xff0c;但是得sudo -i进入root模式 进入/etc/redis下开启redis-server服务 查看6379端口是否可以访问 net…