C++的继承基础和虚继承原理

news2024/9/22 1:36:30

1.继承概念

“继承”是面向对象语言的三大特性之一(封装、继承、多态)。

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

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

2.继承使用

继承发生在“基类/父类”和“派生类/子类”之间,语法形式就是:

class 父类名
{
    //方法
    //属性
};
class 子类名 : 继承方式 父类
{
    //...
};

那这个继承方式又是什么呢?在字面上和我们之前看到的publicprotectedprivate是一样的,但是意义有所不同。继承方式和访问限定符可以相互组合,在父子类中有以下组合:

类成员/继承方式public继承protected继承private继承
子类的public成员子类中仍是public成员子类中变成protected成员子类中变成private成员
子类的protected成员子类中仍是protected成员子类中仍是protected成员子类中变成private成员
子类的private成员子类中不可见,该成员为父类私有子类中不可见,该成员为父类私有子类中不可见,该成员为父类私有

实际上规律也简单:公有>保护>私有,在两个关键字中取小即可,有点类似权限缩小的感觉,当然:您可以选择调用父类的函数来无视这些限定(也就是间接访问)。

下面我们就以“人Person”父类来生成子类“学生Student”和“老师Teacher”:

class Person//父类  
{  
public:  
    void Print()  
    {  
        cout << "name:" << _name << endl;  
        cout << "age:" << _age << endl;  
    }
protected:  
    string _name = "peter";//姓名  
    int _age = 18;//年龄 
};  
//继承后父类的Person的成员(成员函数+成员变量)都会变成子类的一部分

class Student : public Person//继承子类
{  
protected:  
    int _stuid;//学号  
};
class Teacher : public Person
{
protected:
    int _jobid;//工号
};

int main()
{
    Student s;
    Teacher t;
    s.Print();
    t.Print();
    return 0;
}

注意1:实际应用中继承方式大多使用public,还有类内的访问限定符也大多使用publicprotected。其他的继承方式和类内的访问限定符基本上都很少用,并且有很多时候都不推荐使用。

注意2:如果不写继承方式,那么默认继承方式是private(如果是结构体struct就默认public),但是最好还是显式写出继承方式为好。

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

#include <iostream>
#include <string>
using namespace std;
class Person//父类  
{
public:
 void Print()
 {
     cout << "name:" << _name << endl;
     cout << "age:" << _age << endl;
 }
private:
 string _name = "peter";//姓名  
 int _age = 18;//年龄 
};
//继承后父类的Person的成员(成员函数+成员变量)都会变成子类的一部分

class Student : public Person//继承子类
{
 /*void function()
 {
     _name = "student";//失败
     _age = 12;//失败
 }*/
};//不可以直接在类内使用从父类继承来的private变量,包括子类创建出来的对象(不可见),但是依旧可以被Print()使用
class Teacher : public Person
{
 /*void function()
 {
     _name = "teacher";//失败
     _age = 20;//失败
 }*/
};//不可以直接在类内使用从父类继承来的private变量,包括子类创建出来的对象(不可见),但是依旧可以被Print()使用

int main()
{
 Student s;
 Teacher t;
 /* 失败
 s._name;
 s._age;
 t._name;
 t._age;
 */
 s.Print();
 t.Print();
 return 0;
}

注意4:由于父类的private成员在子类中是不能被访问,如果子类成员不想在类外直接被访问,但需要在子类中被访问,就使用限定符为protected即可。从这点也可以看出,保护成员限定符是因为继承才出现的。

#include <iostream>
#include <string>
using namespace std;
class Person//父类  
{
public:
 void Print()
 {
     cout << "name:" << _name << endl;
     cout << "age:" << _age << endl;
 }
protected:
 string _name = "peter";//姓名  
 int _age = 18;//年龄 
};

class Student : public Person//继承子类
{
public:
 void function()
 {
     _name = "student";//成功
     _age = 12;//失败
 }
};
class Teacher : public Person
{
public:
 void function()
 {
     _name = "teacher";//成功
     _age = 20;//失败
 }
};

int main()
{
 Student s;
 Teacher t;
 /* 失败语句
 s._name = "student";
 s._age = 12;
 t._name = "teacher";
 t._age = 20;
 */
 s.function();
 t.function();
 s.Print();
 t.Print();
 return 0;
}

3.切片赋值

公有类继承的子类可以直接赋值给父类,这个过程形象的说法叫做“切割/切片”,也有叫“赋值兼容转化”的。这个过程是纯天然的,没有隐式类型转化的发生(没有临时变量的产生,类似int a = 1; int b = 10; a = b的过程),而这种“切片”的现象实际上在指针和引用上也有体现。

#include <iostream>
#include <string>
using namespace std;
class Person
{
public:
    void Print()
    {
        cout << "name:" << _name << endl;
        cout << "age:" << _age << endl;
    }
protected:
    string _name = "peter";
    int _age = 18;
};

class Student : public Person
{
protected:
    int _stuid;
};

class Teacher : public Person
{
protected:
    int _jobid;
};

int main()
{
    //1.普通类型
    double a = 1.1;
    int b = a;//发生类型转化
    const int& ra = a;//中间会产生临时变量
    int* pa = (int*)&a;
    
    //2.子类切片给父类
    Student std;
    Person per = std;//没有发生类型转化,只是将子类中的成员变量拷贝给父类
    Person& rper = std;//没有产生临时变量,并且使用这一特性就好像给子类做了“切片”,rp本身还是子类,但是却只引用/切出了子类中继承自父类的那一部分成员(因此无需加上const)
    Person* pper = &std;//同理指针也是做了一些“切片”,指针解引用只能看到子类从父类哪里继承的那一部分成员
    
    //3.父类赋值给子类
    //std = per;//不允许
    //Student& rstd = per;//不允许
    //Student* pstd = &per;//不允许


    /* 这里我还暂时理解不了
    Student& rstd = (Student)per;//不允许
    
    pper = &std;
    Student* pstd = (Student*)pper;//允许

    pper = &per;
    pstd = (Student*)pper;//允许,但是有可能会越界
    */
    return 0;
}

那反过来可以么?一般是不可以的,父类不可以直接赋值给子类,因为容易造成子类的属性缺失,有可能会出现问题。

补充:父类的指针和引用可以通过“强制类型转化”赋值给子类的指针和引用,但是必须是父类的指针是指向子类对象时才是安全的(这里的父类如果是多态的,就可以使用RTTI(Run-Time Type Information)dynamic cast来进行识别,然后进行安全转换,这点我们以后再来细说)。

4.隐藏父类

  1. 在继承中父类和子类都有自己独立的作用域

  2. 子类和父类有同名成员,但是子类将屏蔽父对类同名对象的直接访问,这种情况就叫“隐藏/重定义”

  3. 函数也有类似“隐藏”的情况,注意需要和“重载区分(在同一个作用域)”,只需要函数名相同就可以达到“隐藏父类成员函数”

  4. 尽可能不使用同名的成员构成隐藏,不然有的时候会给自己挖坑…

#include <iostream>
#include <string>
using namespace std;
class Person
{
public:
    void Print()
    {
        cout << "name:" << _name << endl;
        cout << "age:" << _age << endl;
    }
protected:
    string _name = "peter";
    int _age = 18;
};

class Student : public Person
{
protected:
    string _name = "student";//父类的_name被子类Student隐藏了
    int _stuid;
public:
    void Print()//父类的Print()被子类Student隐藏了
    {
        cout << "name:" << _name << endl;
        cout << "age:" << _age << endl;//_age没有被子类隐藏,依旧打印从父类继承过来的_age
    }
};

class Teacher : public Person
{
protected:
    string _name = "teacher";//父类的_name被子类Teacher隐藏了
    int _jobid;
public:
    void Print()//父类的Print()被子类Teacher隐藏了
    {
        cout << "name:" << _name << endl;
        cout << "age:" << _age << endl;//_age没有被子类隐藏,依旧打印从父类继承过来的_age
    }
};

int main()
{
    Student s;
    s.Print();
    Teacher t;
    t.Print();
    return 0;
}

但是想要直接访问父类中被子类隐藏的成员怎么办呢?使用作用域解析运算符即可,因为父类的成员只是被隐藏而不是不存在了。

#include <iostream>
#include <string>
using namespace std;
class Person
{
public:
    void Print()
    {
        cout << "name:" << _name << endl;
        cout << "age:" << _age << endl;
    }
protected:
    string _name = "peter";
    int _age = 18;
};

class Student : public Person
{
protected:
    string _name = "student";//父类的_name被子类Student隐藏了
    int _stuid;
public:
    void Print()//父类的Print()被子类Student隐藏了
    {
        cout << "name:" << _name << endl;
        cout << "age:" << _age << endl;//_age没有被子类隐藏,依旧打印父类的_age
    }
};

class Teacher : public Person
{
protected:
    string _name = "teacher";//父类的_name被子类Teacher隐藏了
    int _jobid;
public:
    void Print()//父类的Print()被子类Teacher隐藏了
    {
        cout << "name:" << _name << endl;
        cout << "age:" << _age << endl;//_age没有被子类隐藏,依旧打印父类的_age
    }
};

int main()
{
    Student s;
    s.Print();
    Teacher t;
    t.Print();
    s.Person::Print();
    t.Person::Print();
    return 0;
}

5.子类成员

  1. 子类的构造函数必须调用父类的构造函数初始化继承自父类的那一部分成员,而不能自己在子类中直接初始化。

    如果父类有默认构造函数,哪怕用户不写也会在子类构造函数的初始化列表处,自动调用父类的默认构造函数。

    如果父类没有默认构造函数,则用户必须在子类构造函数的初始化列表阶段显示调用。因此,子类对象初始化先调用父类构造函数再调用子类构造函数。

  2. 子类的析构函数会在被调用完成后自动调用基类的析构函数清理父类成员。因为这样才能 保证派生类对象先清理派生类成员再清理基类成员的顺序,避免出现:用户自己调用父类的析构函数,父类成员先被释放了,但是子类依旧可以使用父类的成员,造成越界访问的现象。

    因此在子类的析构函数内用户不能自己显式调用父类的析构函数,否则会重复调用。

    另外,编译器会对析构函数名进行特殊处理,处理成destrutor(),所以父类析构函数不加virtual的情况下,子类析构函数和父类析构函数构成隐藏关系。后续一些场景中,析构函数需要构成重写(重写的条件之一是函数名相同)

  3. 子类的拷贝构造函数必须调用父类的拷贝构造完成继承自父类的那一部分成员的拷贝初始化。但是父类的拷贝构造函数需要传递一个父类对象过去,如何把继承于父类的成员变量拿出来作为父类对象传给父类的拷贝构造函数呢?使用切片即可,这就是切片的实际应用

  4. 子类的operator=必须要调用父类的operator=完成继承自父类的那一部分成员的复制,同理也是使用切片

    注意:子类是没有办法直接隐藏父类的特殊成员函数的,这也体现了成员函数的特殊性。当然,这除去了析构函数的特殊情况。

  5. 使用父类的成员函数最好一定要带上父类和作用域解析运算符,否则就有可能因为隐藏而出现自己调用自己的情况,就会变成无穷递归(例如:下述代码中子类的Person::Print()调用)。

#include <iostream>
#include <string>
using namespace std;
//1.父类Person
class Person
{
public:
    Person(string name, int age) : _name(name), _age(age)//不是默认的构造函数
    {
        cout << "Person(string nume, int age) : _name(\"peter\"), _age(18)" << endl;
    }
    ~Person()
    {
        cout << "~Person()" << endl;
    }
    Person(Person& p)//不是默认的拷贝构造函数
    {
        cout << "Person(Person& p)" << endl;
        _name = p._name;
        _age = p._age;
    }
    Person& operator=(const Person& p)//不是默认的赋值重载函数
    {
        cout << "Person operator=(const Person& p)" << endl;
        if (this != &p)
        {
            _name = p._name;
            _age = p._age;
        }
        return *this;
    }
    void Print()
    {
        cout << "Print()" << endl;
        cout << "name:" << _name << " age:" << _age << endl;
    }
protected:
    string _name;
    int _age;
};

//2.子类Student
class Student : public Person
{
public:
    Student(string name, int age, int stuid = 00000000) : Person(name, age), _stuid(stuid)//这里调用显示调用父类的构造函数(并且没有明确指定父类),初始化子类中从父类继承过来的成员,然后才初始化子类自己的成员
    {
        cout << "Student(string name, int age, int stuid = 00000000) : Person(name, age), _stuid(stuid)" << endl;
    }
    ~Student()
    {
        cout << "~Student()" << endl;
        //无需自己调用父类函数的析构函数,编译器会自己帮助我们调用
    }
    Student(Student& s) : Person(s), _stuid(s._stuid)//这里使用了切片显式调用父类的拷贝构造函数(可以选择不指定父类),初学者推荐一直显式调用,有助于代码理解和可读性。
    {
        cout << "Student(Student& s) : Person(s), _stuid(s._stuid)" << endl;
    }
    Student& operator=(const Student& p)
    {
        cout << "Student& operator=(const Student& p)" << endl;
        if (this != &p)
        {
            Person::operator=(p);
            _stuid = p._stuid;
        }
        return *this;
    }
    void Print()//构成成员函数隐藏了
    {
        //Print();//这就变成自己调用自己了,此时就不可以不明确父类了
        Person::Print();//指明父类
        cout << "stuid:" << _stuid << endl;
    }
protected:
    int _stuid;
};
int main()
{
    Student s1("limou", 18, 22130800);
    Student s2(s1);
    Student s3("daimou", 22, 22109988);
    s1 = s3;

    s1.Print();
    s2.Print();
    s3.Print();
    return 0;
}

6.不可继承

6.1.友元关系

友元关系是不会被继承的,也就是说,如果父类有一个友元函数,那么这个友元函数只可以使用父类的成员变量,而不可以使用从父类继承过来的子类的内部成员变量。

#include <iostream>
#include <string>
using namespace std;

class Student;//这句是声明,告诉编译器有一个类的存在,让下面在Person内的函数可以通过编译
class Person
{
public:
	friend void Display(const Person& p, const Student& s);
protected:
	string _name;//姓名
};
class Student : public Person
{
protected:
	int _stuNum;//学号
};
void Display(const Person& p, const Student& s)
{
	cout << p._name << endl;
	//cout << s._stuNum << endl;//该语句不正确
}

int main()
{
	Person p;
	Student s;
	Display(p, s);
	return 0;
}

这种情况下只能再定义一个友元关系给子类。

#include <iostream>
#include <string>
using namespace std;

class Student;//这句是声明,告诉编译器有一个类的存在,让下面在Person内的函数可以通过编译
class Person
{
public:
	friend void Display(const Person& p, const Student& s);
protected:
	string _name;//姓名
};
class Student : public Person
{
	friend void Display(const Person& p, const Student& s);
protected:
	int _stuNum;//学号
};
void Display(const Person& p, const Student& s)
{
	cout << p._name << endl;
	cout << s._stuNum << endl;//该语句不正确
}

int main()
{
	Person p;
	Student s;
	Display(p, s);
	return 0;
}

6.2 .静态成员

静态成员变量属于整个类,不仅仅属于父类,也属于子类,因此不能说子类继承静态成员变量,这么说是不准确的。

#include <iostream>
#include <string>
using namespace std;
class Person
{
public:
	Person() { ++_count; }
protected:
	string _name;//姓名
public:
	static int _count;//统计人(对象)的个数
};
int Person::_count = 0;

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

class Graduate : public Student
{
protected:
	string _seminarCourse;//研究科目
};
void TestPerson()
{
	Student s1;
	Student s2;
	Graduate s3;
	Graduate s4;
	cout << "人数:" << Student::_count << endl;
	Person::_count = 0;
	cout << "人数:" << Graduate::_count << endl;
}
int main()
{
	TestPerson();
	return 0;
}

也就是说父类中使用的静态成员变量和子类的静态成员变量是一样的(不过这其实也侧面说明,从父类中的成员变量和子类中的成员变量只是拷贝关系,各自的地址是不一样的)。

7.虚拟继承

7.1.不可能棱形

我之前提到的继承都是“单个父类继承给多个子类”的“单继承”,但是C++在开发之初为了更加符合现实中“多个父类继承个单个子类”的现象,设计了“多继承”。

但是没有想到因此出现了一个新的问题:棱形继承,具有“二义性”和”数据冗余“的两大缺陷。棱形继承是多继承的一种特殊情况,其内容为:”假设有一个父类,其有两个子类,而两个子类又通过多继承得出一个子子类,这个子子类创建出来的对象会从两个子类继承,那么会出现相同的成员,也就是二义性(不知道使用的是哪一个子类的成员)和数据冗余(有可能出现重复相同的成员变量)“。

#include <iostream>
#include <string>
using namespace std;
class Person
{
public:
    Person()
        : _name("peter"), _age(18)
    {
        cout << "Person()" << endl;
    }
public:
    string _name;
    int _age;
};

class Student : public Person
{
public:
    Student()
        :Person(), _stuid(22103)
    {}
public:
    int _stuid;
};

class Teacher : public Person
{
public:
    Teacher()
        :Person(), _jobid(88903)
    {}
protected:
    int _jobid;
};
class Graduate : public Student, public Teacher//多继承
{
protected:
    int _graid;
};

int main()
{
    Graduate g;
    //cout << g._name << endl;//运行失败,具有二义性
    cout << g.Student::_name << endl;//运行成功,成功解决二义性
    cout << g.Teacher::_name << endl;//运行成功,成功解决二义性
    return 0;
}

二义性我们尚且可以通过作用域解析运算符来解决,但是数据冗余还需要使用virtual关键字才能解决。该关键字加在两个子类的继承方式处,但是也因此变得很复杂…

补充:因此多继承会让代码变得复杂,所以Java直接移除了多继承的功能。

7.2.虚继承操作

为什么数据冗余使用virtual关键字就能解决呢?我们来查看一下底层。

这里我们最好不要查看编译器的调试窗口,而是使用内存窗口(因为调试窗口为了方便展示又可能做了一些优化,比如:使用内联函数,有些编译器的调试器会让您觉得这是在调用函数)。

7.2.1.不加关键字

#include <iostream>
#include <string>
using namespace std;
class A//父类A
{
public:
    int _a;
};

class B : public A//子类B
{
public:
    int _b;
};

class C : public A//子类C
{
public:
    int _c;
};

class D : public B, public C//多继承后的子子类D
{
public:
    int _d;
};

int main()
{
    D d;
    d.B::_a = 1;
    d.C::_a = 2;

    d._b = 3;//或者d.B::_b = 3;
    d._c = 4;//或者d.C::_c = 4;

    d._d = 5;
    return 0;
}

可以看到,每一个变量都存储在不同的地址上。

7.2.2.加上关键字

#include <iostream>
#include <string>
using namespace std;
class A//父类A
{
public:
    int _a;
};

class B : virtual public A//子类B
{
public:
    int _b;
};

class C : virtual public A//子类C
{
public:
    int _c;
};

class D : public B, public C//多继承后的子子类D
{
public:
    int _d;
};

int main()
{
    D d;
    d.B::_a = 1;
    d.C::_a = 2;

    d._b = 3;//或者d.B::_b = 3;
    d._c = 4;//或者d.C::_c = 4;

    d._d = 5;
    return 0;
}

在这里插入图片描述
在这里插入图片描述

因此我们成功解决了二义性和数据冗余的问题,需要注意的是,虚拟继承最好不要在其他地方去使用。

7.3.虚继承原理

通过上面的调试我们发现,在解决二义性的时候,好像DBCA处继承的_a成员是在一个地址上的变量?这个应该怎么理解呢?让我们通过调试来理解虚继承的原理。

在这里插入图片描述

BCD中同属于A的部分变成一个公共部分,在继承自BC的部分中多出两个指针,各自指向一块表空间,这些表内存储了指针偏移量信息。利用指针偏移量和表指针的地址即可找到一块公共空间,消除二义性。

那为什么是指针指向一个偏移量表,而不是直接使用指针指向公共空间呢?这些后面多态再来讨论,指针固然能解决这个问题,但是使用偏移量还能再进一步解决多态的问题…
这样子子类在使用从两个子类继承过来的变量的时候,实际上是同一个变量,解决了冗余性和二义性。

既然棱形继承这么复杂,那应该会很少使用吧?实际上您有可能天天用这,iostream类就是通过istream类和ostream类棱形继承来的…

注意:上述两图来源于:Reference - C++ Reference (cplusplus.com)和输入/输出库 - cppreference.com,感兴趣您可以去查看一下。

下面有一道结合了虚拟继承的面试题目供您思考一下,让您更加了解虚继承和初始化列表的初始化顺序。

#include <iostream>
#include <string>
using namespace std;
class A
{
public:
    A(const char* s)
    {
        cout << s << endl;
    }
    ~A() {}
};

class B : virtual public A
{
public:
    B(const char* s1, const char* s2)
        : A(s1)
    {
        cout << s2 << endl;
    }
};

class C : virtual public A
{
public:
    C(const char* s1, const char* s2)
        : A(s1)
    {
        cout << s2 << endl;
    }
};
class D : public B, public C//多继承
{
public:
    D(const char* s1, const char* s2, const char* s3, const char* s4)
        : B(s1, s2), C(s1, s3), A(s1)
    {
        cout << s4 << endl;
    }
};

int main()
{
    D* p = new D("class A", "class B", "class C", "class D");
    //按照初始化列表的根据谁先定义先初始化的原则:
    //先调用A(s1),然后调用B(s1, s2),然后调用C(s1, s3),然后打印"class D"
    //也就是先调用A(s1),然后打印"class B",然后调用A(s1),然后打印"class C",然后调用A(s1),然后打印"class D"
    //由于在D中从B和C中继承过来的A的成员是公共部分,因此只会调用一次构造函数A(s1),并且这个构造函数A(s1)是在D中被调用的
    //于是得到:A、B、C、D
    delete p;
    return 0;
}

8.继承/组合

继承和组合都是一种复用,以下是继承和组合的区别(不过这里只讨论公有方式的继承):

//继承is-a
class A1
{};
class B1 : public A1
{};

//组合has-a
class A2
{};
class B2
{
private:
    A2 _a;
};

那么这个is-ahas-a,是什么呢?一个是“是一个(也就是继承)”的关系,一个是“拥有一个(也就是组合)”的关系。

从“关联关系/耦合度”上来看继承会更高,B1可以直接使用A1的所有成员,但是B2只能使用A2的函数成员,间接调用其他的A2成员(因此对象组合也是一种黑箱/黑盒风格)。

但在软件设计中有一种设计理念就是“低耦合、高内聚”,因此在实践中,应该尽可能去用组合,这样的代码维护性更好(具体还要看实际)。

  1. 如果是为了实现多态,那就必须使用继承(多态我们后面再谈)
  2. 如果描述类之间的关系时,继承和组合都可以,那么应该尽量使用组合

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

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

相关文章

COO、CSR、adj_coo、adj_csr详解:稀疏矩阵与稀疏邻接矩阵的存储格式及转换

文章目录 一、COO二、CSR三、adj_coo四、adj_csr五、格式转换代码 稀疏图&#xff1a;数据结构中对于稀疏图的定义为&#xff1a;有很少条边或弧&#xff08;边的条数 ∣ E ∣ |E| ∣E∣ 远小于 ∣ V ∣ 2 |V|^2 ∣V∣2&#xff09;的图称为稀疏图&#xff0c;反之边的条数 …

Leetcode 231.2的幂

给你一个整数 n&#xff0c;请你判断该整数是否是 2 的幂次方。如果是&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 如果存在一个整数 x 使得 n 2x &#xff0c;则认为 n 是 2 的幂次方。 示例 1&#xff1a; 输入&#xff1a;n 1 输出&#xff1a;tr…

vs2015 报错“无法找到要定向的合适SDK”

关于Visual Studio&#xff1a;找不到合适的SDK来定位 | 码农家园 报错如下&#xff1a; 解决如下&#xff1a;

在排序数组中查找元素的第一个和最后一个位置

给你一个按照非递减顺序排列的整数数组 nums&#xff0c;和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。 如果数组中不存在目标值 target&#xff0c;返回 [-1, -1]。 你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。 示例 1&#xff1a…

办公网络构建

办公网络项目背景 XX州市益智软件科技有限公司是XX市第九职业技术学校校办企业&#xff0c;依托学校人力技术、场地资源&#xff0c;面向市场独立经营、服务社会&#xff0c;主要从事网络设备销售、网络综合布线与网络管理。该公司现租用实训基地二层作为公司的办公经营场地…

为何需关注各ZKP方案的benchmarks?

1. 引言 近期&#xff0c;研究人员和工程人员有大量关于谁是最好的证明系统的争论&#xff1a; 2023年8月29日&#xff0c;StarkWare团队对比了FRI和KZG2023年8月30日&#xff0c;JustinThaler和Srinath Setty讨论FRI和KZG谁的性能更佳&#xff1f; 不过&#xff0c;在深入be…

小谈设计模式(17)—状态模式

小谈设计模式&#xff08;17&#xff09;—状态模式 专栏介绍专栏地址专栏介绍 状态模式关键角色上下文(Context)抽象状态(State)具体状态(Concrete State) 核心思想Java程序实现首先&#xff0c;我们定义一个抽象状态类 State&#xff0c;其中包含一个处理请求的方法 handleRe…

IIC控制器(2):PS端

书接上文&#xff1a; I2C控制器练习&#xff08;1&#xff09;_NoNoUnknow的博客-CSDN博客 SPI协议与FPGA的自动升级和多启动-CSDN博客 本文主要做一些基本知识的补充和工程参考。 写IIC需要注意的事情&#xff1a; 1.查询芯片手册获得slave地址&#xff0c;以及寄存器地址…

『力扣每日一题12』:只出现一次的数字

一、题目 给你一个 非空 整数数组 nums &#xff0c;除了某个元素只出现一次以外&#xff0c;其余每个元素均出现两次。找出那个只出现了一次的元素。 你必须设计并实现线性时间复杂度的算法来解决此问题&#xff0c;且该算法只使用常量额外空间。 示例 1 &#xff1a; 输入&…

MySQL备份恢复

MySQL日志管理 在数据库保存数据时&#xff0c;有时候不可避免会出现数据丢失或者被破坏&#xff0c;这样情况下&#xff0c;我们必须保证数据的安全性和完整性&#xff0c;就需要使用日志来查看或者恢复数据了。 MySQL日志类型 日志类型计入文件中的信息类型错误日志记录启…

Android Studio实现简易计算器(带横竖屏,深色浅色模式,更该按钮颜色,selector,style的使用)

目录 前言 运行结果&#xff1a; 运行截屏&#xff08;p50e&#xff09; apk文件 源码文件 项目结构 总览 MainActivity.java drawable 更改图标的方法&#xff1a; blackbutton.xml bluebuttons.xml greybutton.xml orangebuttons.xml whitebutton.xml layout 布…

uniapp项目实践总结(二十七)苹果应用商店上架教程

导语&#xff1a;之前介绍了如何打包一个苹果安装包文件&#xff0c;如果想要上架苹果 ios 应用商店&#xff0c;那么就来这里学习一下方法吧。 目录 准备材料上架步骤审核事项 准备材料 基本信息 构建版本&#xff1a;需要一个 ipa 格式安装包&#xff1b;logo&#xff1a…

YOLOv5、YOLOv8改进:RepVGG结构

1.简介 论文参考&#xff1a;最新RepVGG结构: Paper 我们所说的“VGG式”指的是&#xff1a; 没有任何分支结构。即通常所说的plain或feed-forward架构。 仅使用3x3卷积。 仅使用ReLU作为激活函数。 主要创新点为结构重参数化。在训练时&#xff0c;网络的结构是多分支进…

计数排序详解

个人主页&#xff1a;Lei宝啊 愿所有美好如期而遇 前言 这种排序在部分情境下出奇地好&#xff0c;也是一种不错的排序 思路 有一个无序数组&#xff0c;我们从中找到最小和最大的数&#xff0c;最大的数减最小的数1的大小就是我们将要新建数组的大小&#xff0c;这个新建数…

14链表-环形链表、龟兔赛跑算法

目录 LeetCode之路——141. 环形链表 分析&#xff1a; 解法一&#xff1a;哈希表 解法二&#xff1a;龟兔赛跑 LeetCode之路——141. 环形链表 给你一个链表的头节点 head &#xff0c;判断链表中是否有环。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针…

美团外卖优惠券小程序 美团优惠券微信小程序 自带流量主模式 带教程

小程序带举牌小人带菜谱流量主模式&#xff0c;挺多外卖小程序的&#xff0c;但是都没有搭建教程 搭建&#xff1a; 1、下载源码&#xff0c;去微信公众平台注册自己的账号 2、解压到桌面 3、打开微信开发者工具添加小程序-把解压的源码添加进去-appid改成自己小程序的 4、…

第十七章:Java连接数据库jdbc(java和myql数据库连接)

1.进入命令行&#xff1a;输入cmd&#xff0c;以管理员身份运行 windowsr 2.登录mysql 3.创建库和表 4.使用Java命令查询数据库操作 添加包 导入包的快捷键 选择第四个 找到包的位置 导入成功 创建java项目 二&#xff1a;连接数据库&#xff1a; 第一步&#xff1a;注册驱动…

单层神经网络

神经网络 人工神经网络&#xff08;Artificial Neural Network&#xff0c;ANN&#xff09;&#xff0c;简称神经网络&#xff08;Neural Network&#xff0c;NN&#xff09;&#xff0c;是一种模仿生物神经网络的结构和功能的数学模型或计算模型。1943年&#xff0c;McCulloc…

软件工程从理论到实践客观题汇总(头歌第九章至第十七章)

九、软件体系结构设计 1、软件体系结构设计概述 2、软件体系结构模型的表示方法 3、软件体系结构设计过程 4、设计初步的软件体系结构 5、重用已有软件资源 6、精化软件体系结构 7、设计软件部署模型 8、文档化和评审软件体系结构设计 十、软件用户界面设计 1、用户界面设计概…

十四天学会C++之第四天(面向对象编程基础)

类和对象是什么&#xff1f; 在C中&#xff0c;类是一种用户定义的数据类型&#xff0c;它可以包含数据成员&#xff08;也就是属性&#xff09;和成员函数&#xff08;也就是方法&#xff09;。类是一种模板或蓝图&#xff0c;用于创建具体的对象。 对象是类的实例&#xff…