C++核心高级编程 --- 4.类和对象

文章目录

    • 第四章:
      • 4.类和对象
        • 4.1 封装
          • 4.1.1 封装的意义
          • 4.1.2 struct与class的区别
        • 4.2 对象的初始化和清理
          • 4.2.1 构造函数和析构函数
          • 4.2.2 构造函数的分类及调用
          • 4.2.3 拷贝构造函数调用时机
          • 4.2.4 构造函数调用规则
          • 4.2.5 深拷贝与浅拷贝
          • 4.2.6 初始化列表
          • 4.2.7 类对象作为类成员
          • 4.2.8 静态成员
        • 4.3 C++对象模型和this指针
          • 4..3.1 成员变量和成员函数分开存储
          • 4.3.2 this指针
          • 4.3.3 空指针访问成员函数
          • 4.3.4 const修饰成员函数
        • 4.4 友元
          • 4.4.1 全局函数做友元
          • 4.4.2 类做友元
          • 4.4.3 成员函数做友元
        • 4.5 运算符重载
          • 4.5.1 加号运算符重载
          • 4.5.2 左移运算符重载
          • 4.5.3 递增运算符重载
          • 4.5.4 赋值运算符重载
          • 4.5.5 关系运算符重载
          • 4.5.6 函数调用运算符重载
        • 4.6 继承
          • 4.6.1 继承基本语法
          • 4.6.2 继承方式
          • 4.6.3 继承中的对象模型
          • 4.6.4 继承中构造和析构顺序
          • 4.6.5 继承同名成员处理方式
          • 4.6.6 继承同名静态成员处理方式
          • 4.6.7 多继承语法
          • 4.6.8 菱形继承
        • 4.7 多态
          • 4.7.1 多态基本概念
          • 4.7.2 纯虚函数和抽象类
          • 4.7.3 虚析构和纯虚析构
          • 4.7.4 多态案例
            • 案例一:制作计算器类
            • 案例二:制作冷饮
            • 案例三:组装电脑

第四章:

4.类和对象

C++面对对象的三大特征:封装继承多态

4.1 封装
4.1.1 封装的意义
  • 将属性和行为作为一个整体,常用来表现生活中的事物

  • 将属性和行为加以权限以便进行控制

语法结构:class 类名 { 访问权限: 属性 / 行为 };

#include <iostream>
using namespace std;const int PI = 3.14;class Circle
{//访问权限//public - 公共权限
public://属性//半径int m_r;//行为//计算圆的面积double calculateCircleArea(){return PI * (m_r * m_r);}};int main()
{//创建一个具体的圆(对象)Circle c1;//对圆(对象)的属性进行赋值操作c1.m_r = 3;cout << "半径为" << c1.m_r << "的面积为" << c1.calculateCircleArea() << endl;system("pause");return 0;
}
#include <iostream>
#include <string>
using namespace std;class Student
{
public:void getID(int id){m_id = id;}void getName(string name){m_name = name;}void getAge(int age){m_age = age;}void showStudent(){cout << m_name << "学号是" << m_id << "年龄是" << m_age << endl;}public:int m_id;string m_name;int m_age;
};
int main()
{Student s1;s1.getID(123456);s1.getName("小明");s1.getAge(18);s1.showStudent();system("pause");return 0;
}

三种访问权限:

  1. public 公共权限

  2. protected 保护权限

  3. private 私有权限

#include <iostream>
using namespace std;// public    - 类内可访问,类外可访问
// protected - 类内可访问,类外不可访问
// private   - 类内可访问,类外不可访问class Person
{
public:string m_Name;
protected:string m_Phone;
private:string m_Password;public:void func(){m_Name = "小红";m_Phone = "HW";m_Password = "ABC123";}
};int main()
{Person p1;p1.m_Name = "小明";p1.m_Phone = "HW";        //报错,protected类外不可访问p1.m_Password = "abc123"; //报错,private类外不可访问system("pause");return 0;
}
4.1.2 struct与class的区别

默认访问权限不同:

  • struct默认权限为公共

  • class 默认权限为私有

#include <iostream>
#include <string>
using namespace std;struct Person1
{string m_Name;
};class Person2
{string m_Name;
};int main()
{Person1 p1;p1.m_Name = "小明";Person2 p2;p2.m_Name = "小红"; //报错system("pause");return 0;
}
4.2 对象的初始化和清理
4.2.1 构造函数和析构函数

对象的初始化和清理是两个重要的安全问题:

  1. 一个对象或变量没有初始状态,对于其使用后的结果是未知的。

  2. 使用完一个对象或变量,没及时清理会造成一定的安全问题。

在C++中使用构造函数和析构函数来解决以上问题,如果我们不提供构造和析构函数,编译器会提供构造函数和析构函数(空实现)。

  • 构造函数:作用于创建对象时对对象的成员属性进行赋值,构造函数有编译器自动调用,无需手动调用。

  • 析构函数:作用于在对象销毁前系统自动调用,进行清理工作。

构造函数语法结构:类名(){}

  1. 无返回值,无需写void

  2. 函数名和类型相同

  3. 可以有参数,因此可发生重载

  4. 程序在调用对象时会自动调用,无序手动调用,指挥调用一次

析构函数语法结构:~类名(){}

  1. 无返回值,无需写void

  2. 函数名于类名相同,在名称前加上符号~

  3. 不可有参数,由此不可发生重载

  4. 程序在对象销毁前会自动调用,无序手动调用,只会调用一次。

#include <iostream>
using namespace std;class Student
{
public:Student(){cout << "Student的构造函数调用" << endl;}~Student(){cout << "Student的析构函数调用" << endl;}
};void test()
{Student s;
}int main()
{test();system("pause");return 0;
}

在这里插入图片描述

4.2.2 构造函数的分类及调用

两种分类方式:

  • 参数分为:有参构造和无参构造

  • 类型分为:普通构造和拷贝构造

三种调用方式:

  • 括号法

  • 显示法

  • 隐式转换法

#include <iostream>
using namespace std;class Student
{
public:Student(){cout << "Student的默认构造函数调用" << endl;}Student(int a){age = a;cout << "Student的有参构造函数调用" << endl;}Student(const Student& s){age = s.age;cout << "Student的拷贝构造函数调用" << endl;}~Student(){cout << "Student的析构函数调用" << endl;}int age;
};void test()
{//1.括号法//Student s1();   //调用默认构造函数时,不要加(),编译器会认为是一个函数的声明 Student s1;       //默认构造函数调用Student s2(18);   //有参构造函数调用Student s3(s2);   //拷贝构造函数调用cout << "s2的年龄是" << s2.age << endl;cout << "s3的年龄是" << s3.age << endl;//2.显示法Student s4;Student s5 = Student(19);Student s6 = Student(s5);//Student(20);    //匿名对象,当前行执行结束后,系统自动回收掉。//Student(s6);      //不要用拷贝构造函数,初始化匿名对象,编译器会认为Student(s5)= Student s5;// //3.隐式转换法Student s7 = 17;  //相当于Student s7 = Student(17)Student s8 = s7;
}
int main()
{test();system("pause");return 0;
}
4.2.3 拷贝构造函数调用时机

三种情况:

  • 使用一个已经创建完毕的对象来初始化一个新的对象

  • 值传递的方式给函数参数传值

  • 以值方式返回局部对象

#include <iostream>
using namespace std;class Student
{
public:Student(){cout << "Student的默认构造函数调用" << endl;}Student(int age){cout << "Student的有参构造函数调用" << endl;m_age = age;}Student(const Student& s){cout << "Student的拷贝构造函数调用" << endl;m_age = s.m_age;}~Student(){cout << "Student的析构构造函数调用" << endl;}int m_age;
};//使用一个已经创建完毕的对象来初始化一个新的对象
void test01()
{Student s1(18);Student s2(s1);cout << "s2的年龄是" << s2.m_age << endl;
}void Work1( Student s)
{}
//值传递的方式给函数参数传值
void test02()
{Student s3;Work1(s3);
}Student work2()
{Student s5;cout << &s5 << endl;return s5;
}//以值方式返回局部对象
void test03()
{Student s4 = work2();cout << &s4 << endl;
}int main()
{test01();test02();test03();system("pause");return 0;
}
4.2.4 构造函数调用规则

默认情况下,C++编译器中至少会给一个类添加三个函数

  • 默认构造函数(无参,函数体为空)

  • 默认拷贝构造函数,对属性进行值拷贝

  • 默认析构函数(无参,函数体为空)

调用规则:

  1. 如果用户定义了有参构造函数,C++将不会提供默认无参构造函数,但会提供默认拷贝构造函数。

  2. 如果用户定义了拷贝构造函数,C++将不会提供其他的构造函数。

//如果用户定义了有参构造函数,C++将不会提供默认无参构造函数,但会提供默认拷贝构造函数。
#include <iostream>
using namespace std;class Student
{
public:Student(){cout << "Student的默认构造函数调用" << endl;}Student(int age){cout << "Student的有参构造函数调用" << endl;m_age = age;}/*Student(const Student& s){cout << "Student的拷贝构造函数调用" << endl;m_age = s.m_age;}*/~Student(){cout << "Student的析构函数调用" << endl;}int m_age;
};void test01()
{Student s1;s1.m_age = 18;Student s2(s1);cout << "s2的年龄是:" << s2.m_age << endl;
}//void test02()
//{
//    Student s3(20);
//    Student s4(s3);
//    cout << "s4的年龄是" << s4.m_age << endl;
//}int main()
{test01();//test02();system("pause");return 0;
}

在这里插入图片描述

#include <iostream>
using namespace std;class Student
{
public:/*Student(){cout << "Student的默认构造函数调用" << endl;}*/Student(int age){cout << "Student的有参构造函数调用" << endl;m_age = age;}Student(const Student& s){cout << "Student的拷贝构造函数调用" << endl;m_age = s.m_age;}~Student(){cout << "Student的析构函数调用" << endl;}int m_age;
};//void test01()
//{
//    Student s1;
//    s1.m_age = 18;
//    Student s2(s1);
//    cout << "s2的年龄是:" << s2.m_age << endl;
//}void test02()
{Student s3(20);Student s4(s3);cout << "s4的年龄是" << s4.m_age << endl;
}int main()
{/*test01();*/test02();system("pause");return 0;
}

在这里插入图片描述

4.2.5 深拷贝与浅拷贝

浅拷贝:赋值拷贝操作

深拷贝:在堆区重新申请空间,进行拷贝操作

#include <iostream>
using namespace std;class Student
{
public:Student(){cout << "Student的默认构造函数调用" << endl;}Student(int age, int weight){m_age = age;m_weight = new int(weight);cout << "Student的有参构造函数调用" << endl;}Student(const Student& s){cout << "Student的拷贝构造函数调用" << endl;m_age = s.m_age;m_weight = new int(*s.m_weight);}~Student(){if (m_weight != NULL){delete m_weight;m_weight = NULL;}cout << "Student的析构函数调用" << endl;}int m_age;int* m_weight;
};void test01()
{Student s1(18, 120);cout << "s1的年龄是" << s1.m_age << ",体重是:" << *s1.m_weight << endl;Student s2(s1);cout << "s2的年龄是" << s2.m_age << ",体重是:" << *s2.m_weight << endl;
}int main()
{test01();system("pause");return 0;
}
4.2.6 初始化列表

作用:初始化属性

语法结构:构造函数():属性1(值1), 属性2(值2)…{}

#include <iostream>
using namespace std;class Student
{
public:Student():m_age(18),m_height(180){}Student(int age, int height) :m_age(age), m_height(height){}int m_age;int m_height;
};void test01()
{Student s1;cout << "s1的年龄是" << s1.m_age << endl;cout << "s1的身高是" << s1.m_height << endl;Student s2(20,175);cout << "s2的年龄是" << s2.m_age << endl;cout << "s2的身高是" << s2.m_height << endl;
}int main()
{    test01();system("pause");return 0;
}

在这里插入图片描述

4.2.7 类对象作为类成员

C++的类中成员可以是另一个类的对象,称该成员为对象成员。

#include <iostream>
#include <string>
using namespace std;class Phone
{
public:Phone(string pName){m_PName = pName;cout << "Phone的构造函数调用" << endl;}~Phone(){cout << "Phone的析构函数调用" << endl;}string m_PName;
};class Student
{
public:Student(string name, string pName):m_Name(name), m_Phone(pName){/*    m_Name = name;m_Phone = pName;*/cout << "Student的构造函数调用" << endl;}~Student(){cout << "Student的析构函数调用" << endl;}string m_Name;Phone m_Phone;
};void test01()
{Student s1("小明", "HW");cout << s1.m_Name << "使用" << s1.m_Phone.m_PName << "手机" << endl;
}int main()
{test01();system("pause");return 0;
}

在这里插入图片描述

总结:当其他类对象作为本类成员时,构造时先构造类对象,再构造自身,析构的顺序与构造相反。

4.2.8 静态成员

在成员变量和成员函数前加上关键字static,称为静态成员。

分类

  • 静态成员变量

      所有对象共享同一份数据在编译阶段时分配内存类内声明,类外初始化
    
  • 静态成员函数
    所有对象共享同个函数
    静态成员函数只能访问静态成员变量

静态成员变量

#include <iostream>
using namespace std;class Student
{
public:static int m_A;private:static int m_B;
};int Student::m_A = 10;
int Student::m_B = 20;void test01()
{//静态成员变量两种访问方式//通过对象Student s1;s1.m_A = 100;cout << "s1的m_A值为:" << s1.m_A << endl;Student s2;s2.m_A = 200;cout << "s1的m_A值为:" << s1.m_A << endl; //共享同一份数据cout << "s2的m_A值为:" << s2.m_A << endl;//通过类名cout << "s1的m_A值为:" << Student::m_A << endl;cout << "s2的m_A值为:" << Student::m_A << endl;
}int main()
{test01();system("pause");return 0;
}

静态成员函数

#include <iostream>
using namespace std;class Student
{
public:static void func(){m_A = 10; //静态成员函数可访问静态成员变量//m_B = 20; //报错,静态成员函数不可访问非静态成员变量cout << "statc void func函数的调用" << endl;}
private:static void func2(){m_A = 10;//m_B = 20;cout << "statc void func2函数的调用" << endl;}
public:static int m_A;int m_B;
};int Student::m_A = 10;
//int Student::m_B = 20; //报错,非静态成员变量不可在类外定义void test01()
{//通过对象访问Student s1;s1.func();//通过类名访问Student::func();//Student::func2(); //报错,类外访问不到私有静态成员函数
}int main()
{test01();system("pause");return 0;
}
4.3 C++对象模型和this指针
4…3.1 成员变量和成员函数分开存储

在C++中,只有非静态成员变量才属于类的对象上。

#include <iostream>
using namespace std;class Student
{
public:int m_A;        //非静态成员变量 属于类的对象上static int m_B; //静态成员变量 不属于类的对象上void func(){}   //非静态成员函数 属于类的对象上void func2(){}  //静态成员变量 不属于类的对象上
};void test01()
{Student s1;cout << sizeof(s1) << endl;
}int main()
{test01();system("pause");return 0;
}

补充:空对象占用内存空间为1,C++编译器会给每个空对象也分配一个字节空间,以便区分对象占用内存的位置。

4.3.2 this指针

定义:this指针指向被调用的成员函数所属的对象,隐含每一个非静态成员函数内的一种指针。

作用

  • 当形参和成员变量同名时,可用this指针来进行区分

  • 在类的非静态成员函数中返回对象本身,可用return *this

#include <iostream>
using namespace std;class Student
{
public:Student(int age){//当形参和成员变量同名时,可用this指针来进行区分this->age = age;}Student& stuAddAge(Student &s){this->age += s.age;//在类的非静态成员函数中返回对象本身,可用return *thisreturn *this;}int age;
};void test01()
{Student s1(18);cout << "s1的年龄是" << s1.age << endl; //s1的年龄是18Student s2(20); s2.stuAddAge(s1);cout << "s2的年龄是" << s2.age << endl; //s2的年龄是38}int main()
{test01();system("pause");return 0;
}
4.3.3 空指针访问成员函数
#include <iostream>
using namespace std;class Student
{
public:void showClassName(){cout << "This is Student" << endl;}void showClassAge(){if (this == NULL)return;//如果没有if判断的话会报错,因为传入的指针为NULLcout << "m_age = " << this->m_age << endl;}int m_age;
};void test01()
{Student* s = NULL;s->showClassName();s->showClassAge();
}int main()
{test01();system("pause");return 0;
}

补充:空指针是可以调用成员函数的,但要注意是否用到this指针,用到的话需要加以判断来保证代码的健壮性。

4.3.4 const修饰成员函数

常函数

  • 成员函数后加上const后,称该函数为常函数

  • 常函数内不可修改成员的属性

  • 成员属性声明时加上关键字mutable后,在常函数依旧可修改

常对象

  • 声明对象前加上const后,称该对象为常对象

  • 常对象只能调用常函数

#include <iostream>
using namespace std;class Student
{
public://this指针本质上是一个指针常量,指针的指向是不可修改的//在成员函数后加上const,修饰的是this指向,让指针指向的值也不可被修改//const Student * const thisvoid modifyStudent() const{//this->m_A = 100; //报错this->m_B = 200;//this = NULL; //this指针不能修改指针的指向}void func(){cout << "This is func" << endl;}int m_A;mutable int m_B;   //特殊变量,即使在常函数中,也可修改
};void test01()
{Student s;s.modifyStudent();
}//常对象
void test02()
{const Student s2; //常对象//s2.m_A = 100;     //报错s2.m_B = 200;s2.modifyStudent();//s2.func();        //报错,常对象只能调用常函数,不能调用普通成员函数,普通成员函数可以修改属性
}int main()
{test01();test02();system("pause");return 0;
}
4.4 友元

作用:让一个函数或类访问另一个类中私有成员。

关键字:friend

三种实现

  • 全局函数做友元

  • 类做友元

  • 成员函数做友元

4.4.1 全局函数做友元
#include <iostream>
using namespace std;class House
{friend void Visit(House* house);
public:House(){m_LivingRoom = "客厅";m_BedRoom = "卧室";}
public:string m_LivingRoom;
private:string m_BedRoom;
};void Visit(House* house)
{cout << "正在参观" << house->m_LivingRoom << endl;cout << "正在参观" << house->m_BedRoom << endl;
}void test01()
{House h;Visit(&h);
}int main()
{test01();system("pause");return 0;
}
4.4.2 类做友元
#include <iostream>
#include <string>
using namespace std;class House;
class goodfriend
{
public:goodfriend();void visit();private:House* house;};class House
{friend class goodfriend;public:House();public:string m_LivingRoom;
private:string m_BedRoom;
};House::House()
{this->m_LivingRoom = "客厅";this->m_BedRoom = "卧室";
}goodfriend::goodfriend()
{house = new House;
}void goodfriend::visit()
{cout << "正在参观" << house->m_LivingRoom << endl;cout << "正在参观" << house->m_BedRoom << endl;
}void test01()
{goodfriend gf;gf.visit();
}int main()
{test01();system("pause");return 0;
}
4.4.3 成员函数做友元
#include <iostream>
#include <string>
using namespace std;class House;
class goodfriend
{
public:goodfriend();void visit();void visit2();private:House* house;};class House
{friend void goodfriend::visit();public:House();public:string m_LivingRoom;
private:string m_BedRoom;
};House::House()
{this->m_LivingRoom = "客厅";this->m_BedRoom = "卧室";
}goodfriend::goodfriend()
{house = new House;
}void goodfriend::visit()
{cout << "正在参观" << house->m_LivingRoom << endl;cout << "正在参观" << house->m_BedRoom << endl;
}void goodfriend::visit2()
{cout << "正在参观" << house->m_LivingRoom << endl;//cout << "正在参观" << house->m_BedRoom << endl;
}void test01()
{goodfriend gf;gf.visit();gf.visit2();
}int main()
{test01();system("pause");return 0;
}
4.5 运算符重载

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

4.5.1 加号运算符重载

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

#include <iostream>
using namespace std;class Student
{
public://成员函数加号运算符重载/*Student operator+(Student& s){Student temp;temp.m_A = this->m_A + s.m_A;temp.m_B = this->m_B + s.m_B;return temp;}*/int m_A;int m_B;
};//全局函数加号运算符重载
Student operator+(Student& s1, Student& s2)
{Student temp;temp.m_A = s1.m_A + s2.m_A;temp.m_B = s1.m_B + s2.m_B;return temp;
}//函数重载
Student operator+(Student& s, int val)
{Student temp;temp.m_A = s.m_A + val;temp.m_B = s.m_B + val;return temp;
}void test01()
{Student s1;s1.m_A = 10;s1.m_B = 20;Student s2;s2.m_A = 10;s2.m_B = 20;Student s3 = s1 + s2;Student s4 = s1 + 10;//成员函数本质调用//Student s3 = s1.operator+(s2);//全局函数本质调用//Student s3 = operator+(s1, s3);cout << "s3 - m_A = " << s3.m_A << endl;cout << "s3 - m_B = " << s3.m_B << endl;cout << "s4 - m_A = " << s4.m_A << endl;cout << "s4 - m_B = " << s4.m_B << endl;
}int main()
{test01();system("pause");return 0;
}
4.5.2 左移运算符重载

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

#include <iostream>
using namespace std;class Student
{friend ostream& operator<<(ostream& cout, Student& s);
public:Student(int a, int b){m_A = a;m_B = b;}private:int m_A;int m_B;
};//使用成员函数重载左移运算符 s1.operator<<(cout) s1 << cout,但无法实现,因为cout在左侧
//只能使用全局函数重载左移运算符
//全局函数重载调用本质 operator<< (cout , p)  cout << p
ostream& operator<<(ostream &cout, Student &s)
{cout << "m_A = " << s.m_A << " " << "m_B = " << s.m_B << endl;return cout;
}void test01()
{Student s1(10, 20);cout << s1 << endl;cout << "hello C++" << endl;
}int main()
{test01();system("pause");return 0;
}
4.5.3 递增运算符重载

作用:实现自己的整型数据。

#include <iostream>
using namespace std;class MyIntData                        
{friend ostream& operator<<(ostream& cout, MyIntData myInt);
public:MyIntData(){m_Num = 0;}//前置++MyIntData& operator++(){m_Num++;return *this; //将自身返回}//后置++MyIntData operator++(int){MyIntData temp = *this; //先记录当前结果m_Num++;                //递增return temp;            //将记录结果返回}
private:int m_Num;
};ostream& operator<<(ostream& cout, MyIntData myInt)
{cout << myInt.m_Num;return cout;
}void test01()
{MyIntData myInt;cout << ++myInt << endl;cout << myInt << endl;
}void test02()
{MyIntData myInt2;cout << myInt2++ << endl;cout << myInt2 << endl;
}int main()
{test01();test02();system("pause");return 0;
}
4.5.4 赋值运算符重载

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

  • 默认构造函数(无参,函数体为空)

  • 默认析构函数(无参,函数体为空)

  • 默认拷贝构造函数,对属性进行值拷贝

  • 赋值运算符 operator=,对属性进行值拷贝

类中有属性指向堆区,赋值操作时也会出现深浅拷贝问题。

#include <iostream>
using namespace std;class Student
{
public:Student(int age){m_Age = new int(age);}~Student(){if (m_Age != NULL){delete m_Age;m_Age = NULL;}}Student& operator=(Student& s){if (m_Age != NULL){delete m_Age;m_Age = NULL;}m_Age = new int(*s.m_Age);return *this;}int *m_Age;
};void test01()
{Student s1(18);Student s2(20);Student s3(19);s1 = s2 = s3;cout << "s1的年龄是:" << *s1.m_Age << endl;cout << "s2的年龄是:" << *s2.m_Age << endl;cout << "s3的年龄是:" << *s2.m_Age << endl;
}int main()
{test01();system("pause");return 0;
}
4.5.5 关系运算符重载

作用:可让两个自定义类型对象进行比较操作。

#include <iostream>
#include <string>
using namespace std;class Student
{
public:Student(string name, int age){m_Name = name;m_Age = age;}bool operator==(Student& s){if (this->m_Age == s.m_Age && this->m_Name == s.m_Name)return true;return false;}bool operator!=(Student& s){if (this->m_Age == s.m_Age && this->m_Name == s.m_Name)return false;return true;}string m_Name;int m_Age;
};void test01()
{Student s1("小明", 18);Student s2("小红", 18);if (s1 == s2){cout << "s1 = s2" << endl;}else{cout << "s1 != s2" << endl;}if (s1 != s2){cout << "s1 != s2" << endl;}else{cout << "s1 = s2" << endl;}
}int main()
{test01();system("pause");return 0;
}
4.5.6 函数调用运算符重载
  • 函数调用运算符()也可重载

  • 重载后使用的方式极其像函数的调用方式,因此称为仿函数

  • 仿函数无固定写法,比较灵活

#include <iostream>
using namespace std;class Printf
{
public:void operator()(string test){cout << test << endl;}
};void Printf02(string test)
{cout << test << endl;
}class numAdd
{
public:int operator()(int value1, int value2){return value1 + value2;}
};void test01()
{Printf p;p("Hello C++");  //仿函数Printf02("Hello C++");
}void test02()
{numAdd a;int result = a(10, 20);cout << "result = " << result << endl;cout << " numAdd()(20, 20) = " << numAdd()(20, 20) << endl; //匿名对象调用}int main()
{test01();test02();system("pause");return 0;
}
4.6 继承

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

类与类存在着特殊的关系

在这里插入图片描述

下级成员不仅有上一级的共性,还会有自己的特性,利用继承技术,可减少重复代码。

4.6.1 继承基本语法

语法结构:class 子类 : 继承方式 父类

#include <iostream>
using namespace std;class BasePage
{
public:void top(){cout << "欢迎来到abc线上购物中心" << endl;}void left(){cout << "Shoes、Water、Paper、Clothes...(公共分类列表)" << endl;}void bottom(){cout << "服务中心、购物指导、联系我们...(公共底部)" << endl;}
};class Shoes :public BasePage
{
public:void conter(){cout << "You can buy shoes" << endl;}
};class Water :public BasePage
{
public:void conter(){cout << "You can buy water" << endl;}
};class Paper :public BasePage
{
public:void conter(){cout << "You can buy paper" << endl;}
};class Clothes :public BasePage
{
public:void conter(){cout << "You can buy clothes" << endl;}
};void test01()
{cout << "鞋子购买页面如下:" << endl;Shoes sh;sh.top();sh.bottom();sh.left();sh.conter();cout << "--------------------" << endl;cout << "矿泉水购买页面如下:" << endl;Water wt;wt.top();wt.bottom();wt.left();wt.conter();cout << "--------------------" << endl;cout << "纸巾购买页面如下:" << endl;Paper pr;pr.top();pr.bottom();pr.left();pr.conter();cout << "--------------------" << endl;cout << "衣服购买页面如下:" << endl;Clothes cl;cl.top();cl.bottom();cl.left();cl.conter();
}int main()
{test01();return 0;
}

补充

  • 子类也称派生类

  • 父类也称基类

派生类成员含两大部分

  1. 从积累继承过来的

  2. 自己增加的成员

基类继承来的成员体现其共性,新增成员体现其个性。

4.6.2 继承方式

继承语法结构:class 子类 : 继承方式 父类

三种继承方式

  • 公共继承

  • 保护继承

  • 私有继承

在这里插入图片描述

#include <iostream>
using namespace std;class Father
{
public:int m_A;
protected:int m_B;
private:int m_C;
};class Son1 :public Father
{
public:void func(){m_A = 10; //父类中的公共权限成员,到子类依然是公共权限m_B = 20; //父类中的保护权限成员,到子类依然是保护权限//m_C = 30; //报错,父类中的私有权限成员,子类没权限访问}
};class Son2 :protected Father
{
public:void func(){m_A = 10;    //父类中的公共权限成员,到子类变保护权限m_B = 20;    //父类中的保护权限成员,到子类依然是保护权限//m_C = 30;  //报错,父类中的私有权限成员,子类没权限访问}
};class Son3 :private Father
{
public:void func(){m_A = 10;      //父类中的公共权限成员,到子类变私有权限m_B = 20;      //父类中的保护权限成员,到子类变私有权限//m_C = 30;      报错,父类中的私有权限成员,子类没权限访问}
};class GrandSon :public Son3
{
public:void func(){//m_A = 10;      //报错,m_A变为私有权限,访问不到//m_B = 20;      //报错,m_B变为私有权限,访问不到}
};void test01()
{Son1 s1;s1.m_A = 10;//s1.m_B = 20;  //报错,m_B是保护权限,类外访问不到Son2 s2;//s2.m_A = 10;   //报错,m_A是保护权限,类外访问不到//s2.m_B = 20;   //报错,m_B是保护权限,类外访问不到Son3 s3;//s3.m_A = 10;   //报错,m_A是私有权限,类外访问不到//s3.m_B = 20;     //报错,m_B是私有权限,类外访问不到
}int main()
{test01();system("pause");return 0;
}#include <iostream>
using namespace std;class Father
{
public:int m_A;
protected:int m_B;
private:int m_C;
};class Son1 :public Father
{
public:void func(){m_A = 10; //父类中的公共权限成员,到子类依然是公共权限m_B = 20; //父类中的保护权限成员,到子类依然是保护权限//m_C = 30; //报错,父类中的私有权限成员,子类没权限访问}
};class Son2 :protected Father
{
public:void func(){m_A = 10;    //父类中的公共权限成员,到子类变保护权限m_B = 20;    //父类中的保护权限成员,到子类依然是保护权限//m_C = 30;  //报错,父类中的私有权限成员,子类没权限访问}
};class Son3 :private Father
{
public:void func(){m_A = 10;      //父类中的公共权限成员,到子类变私有权限m_B = 20;      //父类中的保护权限成员,到子类变私有权限//m_C = 30;      报错,父类中的私有权限成员,子类没权限访问}
};class GrandSon :public Son3
{
public:void func(){//m_A = 10;      //报错,m_A变为私有权限,访问不到//m_B = 20;      //报错,m_B变为私有权限,访问不到}
};void test01()
{Son1 s1;s1.m_A = 10;//s1.m_B = 20;  //报错,m_B是保护权限,类外访问不到Son2 s2;//s2.m_A = 10;   //报错,m_A是保护权限,类外访问不到//s2.m_B = 20;   //报错,m_B是保护权限,类外访问不到Son3 s3;//s3.m_A = 10;   //报错,m_A是私有权限,类外访问不到//s3.m_B = 20;     //报错,m_B是私有权限,类外访问不到
}int main()
{test01();system("pause");return 0;
}
4.6.3 继承中的对象模型

从父类继承过来的成员,哪些是属于子类对象中呢?

#include <iostream>
using namespace std;class Father
{
public:int a;
protected:int b;
private:int c;
};class Son : public Father
{
public:int d;
};void test01()
{//父类中的所有非静态成员属性都被被子类所继承下去//父类中的私有成员属性也会被继承下去,只是被编译器隐藏了,因此访问不到cout << "sizeof(Son)的结果为" << sizeof(Son) << endl; //16
}int main()
{test01();system("pause");return 0;
}
4.6.4 继承中构造和析构顺序

子类在继承父类后,创建子类对象,也会调用父类的构造函数。

#include <iostream>
using namespace std;class Father
{
public:Father(){cout << "Father的构造函数调用" << endl;}~Father(){cout << "Father的析构函数调用" << endl;}
};class Son : public Father
{
public:Son(){cout << "Son的构造函数调用" << endl;}~Son(){cout << "Son的析构函数调用" << endl;}
};void test01()
{Son s1;
}int main()
{test01();system("pause");return 0;
}

在这里插入图片描述

总结:先构造父类,再构造子类,析构的顺序跟构造相反。

4.6.5 继承同名成员处理方式

子类与父类中有同名的成员,怎样通过子类对象,访问到子类或父类中同名的数据呢?

  • 访问子类同名成员 直接访问即可

  • 访问父类同名成员 加作用域即可

#include <iostream>
using namespace std;class Father
{
public:Father(){m_A = 10;}void func(){cout << "Father - func调用" << endl;}void func(int ){cout << "Father - func(int)调用" << endl;}int m_A;
};class Son :public Father
{
public:Son(){m_A = 20;}void func(){cout << "Son - func调用" << endl;}int m_A;
};void test01()
{Son s1;cout << "s1 m_A = " << s1.m_A << endl;cout << "Father m_A = " << s1.Father::m_A << endl;Son s2;s2.func();s2.Father::func();//s2.func(30)  //报错//子类中如果出现与父类同名的成员函数,子类会将父类中的所有同名成员函数隐藏
}int main()
{test01();system("pause");return 0;
}

总结:子类对象可直接访问子类中同名成员,子类对象加上作用域可访问到父类同名成员,当子类与父类有同名的成员函数,子类会隐藏父类中同名成员函数,加作用域可访问到父类中同名函数。

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

继承中同名的静态成员怎样在子类对象上进行访问呢?

静态成员与非静态成员出现同名的情况,处理方式一样

  • 访问子类同名成员 直接访问即可

  • 访问父类同名成员 加作用域即可

#include <iostream>
using namespace std;class Base
{
public:static int m_A;static void func(){cout << "Base - static void func调用" << endl;}static void func(int){cout << "Son - static void func(int)调用" << endl;}
};
int Base::m_A = 10; //类外初始化class Son :public Base
{
public:static int m_A;static void func(){cout << "Son - static void func调用" << endl;}
};
int Son::m_A = 20;//同名静态成员属性
void test01()
{//通过对象访问Son s1;cout << "s1 m_A = "  << s1.m_A << endl;cout << "Base m_A = " << s1.Base::m_A << endl;//通过类名访问cout << "s1 m_A = " << Son::m_A << endl;cout << "Base m_A = " << Son::Base::m_A << endl;
}//同名静态成员函数
void test02()
{//通过对象访问Son s2;s2.func();s2.Base::func();//通过类名访问Son::func();Son::Base::func();Son::Base::func(10);}int main()
{test01();test02();system("pause");return 0;
}

总结:非静态成员有两种访问方式,对象访问和类名访问。

4.6.7 多继承语法

在C++中允许一个类继承一个类

语法结构:class 子类 : 继承方式 父类1, 继承方式 父类2…

#include <iostream>
using namespace std;class Base1
{
public:Base1(){m_A = 10;}int m_A;
};class Base2
{
public:Base2(){m_A = 20;}int m_A;
};class Son :public Base1, public Base2
{
public:Son(){m_B = 30;m_C = 40;}int m_B;int m_C;
};void test01()
{Son s1;cout << "sizeof(Son) = " << sizeof(Son) << endl;//父类中出现同名成员,需加作用域区分cout << "Base1 m_A = " << s1.Base1::m_A << endl;cout << "Base2 m_A = " << s1.Base2::m_A << endl;
}int main()
{test01();system("pause");return 0;
}

在这里插入图片描述

4.6.8 菱形继承

概念

  • 两个派生类继承同一个基类

  • 又有某个类同时继承这两个派生类

在这里插入图片描述

1.羊继承了动物的数据,驼也继承了动物的数据,羊驼使用数据时,会产生二义性。

2.羊驼继承动物的数据两份,其实该数据只需要一份即可。

#include <iostream>
using namespace std;class Animal
{
public :int m_Age;
};class Sheep :virtual public  Animal //加上关键字virtual变成虚继承
{};
//Animal类称为虚基类
class Camel :virtual public Animal  //加上关键字virtual变成虚继承
{};class SheepCamel :public Sheep, public Camel
{};void test01()
{SheepCamel sc;sc.Sheep::m_Age = 18;sc.Camel::m_Age = 18;//菱形继承,两个父类拥有相同数据,需加作用域区分cout << "sc.Sheep::m_Age = " << sc.Sheep::m_Age << endl;cout << "sc.Camel::m_Age = " << sc.Camel::m_Age << endl;cout << "sc.m_Age = " << sc.m_Age << endl;//菱形继承导致数据有两份,资源浪费,可使用虚继承来解决该问题 
}int main()
{test01();system("pause");return 0;
}

在这里插入图片描述

4.7 多态

多态的优点:

  • 代码组织结构清晰

  • 可读性强

  • 利于前后期的扩展及维护

多态使用条件:父类指针或引用指向子类对象。

4.7.1 多态基本概念

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

多态分两类

  • 静态多态:函数重载与运算符重载属于静态多态,复用函数名

  • 动态多态:派生类与虚函数实现运行时多态

静态多态与动态多态区别:

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

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

//静态多态的函数地址早绑定 - 编译阶段确定函数地址
#include <iostream>
using namespace std;class Afternoon 
{
public:void Drink(){cout << "Water" << endl;}
};class Person1 :public Afternoon
{
public:void Drink(){cout << "Coffee" << endl;}
};class Person2 : public Afternoon
{
public:void Drink(){cout << "Tea" << endl;}
};void doWork(Afternoon &af) 
{af.Drink();
}void test01()
{Person1 p1;doWork(p1); //结果:Water
}int main()
{test01();system("pause");return 0;
}
//动态多态的函数地址晚绑定 - 运行阶段确定函数地址
#include <iostream>
using namespace std;class Afternoon
{
public:virtual void Drink() //虚函数{cout << "Water" << endl;}
};class Person1 :public Afternoon
{
public:void Drink(){cout << "Coffee" << endl;}
};class Person2 : public Afternoon
{
public:void Drink(){cout << "Tea" << endl;}
};//如果想要喝咖啡,需要让函数地址不提前绑定,在运行时绑定,地址晚绑定
void doWork(Afternoon& af)
{af.Drink();
}void test01()
{Person1 p1;doWork(p1); //结果:CoffeePerson2 p2;doWork(p2); //结果:Tea
}int main()
{test01();system("pause");return 0;
}

补充:

动态多态满足条件:

  1. 继承关系

  2. 子类重写父类的虚函数

重写:函数返回值类型 函数名 参数列表完全一致。

4.7.2 纯虚函数和抽象类

父类中虚函数实现是无意义的,主要用来调用子类重写内容,我们可将虚函数改成纯虚函数。

语法结构:virtual 返回值类型 函数名 (参数列表) = 0 ;

当某个类中有了纯虚函数,那么也称该类为抽象类。

抽象类特点

  • 无法实例化对象

  • 子类必须重写抽象类中纯虚函数,否则也属于抽象类

#include <iostream>
using namespace std;class Base
{
public:virtual void func() = 0; //纯虚函数
};class Son : public Base      //抽象类的子类必须重写父类中的纯虚函数,不然也属于抽象类
{
public:virtual void func(){cout << "Son - func调用" << endl;}
};void test01()
{//Base b1;   //报错,抽象类无法实例化对象//new Base;  //报错,抽象类无法实例化对象//Son s1;    //类必须重写父类中的纯虚函数,否则无法实例化对象Base* bs = new Son;bs->func();  //结果:Son - func调用
}int main()
{test01();system("pause");return 0;
}
4.7.3 虚析构和纯虚析构

多态使用时,如子类有属性开辟到堆区上,那父类指针在释放时无法调用子类的析构代码
将父类的析构函数改为虚析构或者纯虚析构即可解决以上问题
虚析构和纯虚析构的共性

  • 可解决父类指针释放子类对象问题

  • 都要有具体函数的实现

虚析构和纯虚析构的区别

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

虚析构语法结构: virtual ~类名(){}

纯虚析构语法结构:virtual ~类名() = 0;

                               类名::类名(){}
#include <iostream>
#include <string>
using namespace std;class Eat
{
public:Eat(){cout << "Eat 构造函数调用" << endl;}virtual void Drinking() = 0;//virtual ~Eat() //加上virtual,变成虚析构函数//{//    cout << "Drinking 虚析构函数调用" << endl;//}virtual ~Eat() = 0;
};Eat::~Eat() //纯虚析构,该类属于抽象类,无法实例化对象
{cout << "Eat 纯虚析构函数调用" << endl;
}class Coffee :public Eat
{
public:Coffee(string feeding){cout << "Coffee 构造函数调用" << endl;m_Feeding = new string(feeding);}virtual void Drinking(){cout << *m_Feeding << "加入咖啡" << endl;}~Coffee(){cout << "Coffee 析构函数调用" << endl;if (this->m_Feeding != NULL){delete m_Feeding;m_Feeding = NULL;}}
public:string *m_Feeding;
};void test01()
{Eat* eat = new Coffee("牛奶");eat->Drinking();//通过父类指针释放,导致子类对象清理不干净,容易造成内存泄漏//给基类加一个虚析构函数即可解决父类指针释放子类对象问题delete eat;
}int main()
{test01();system("pause");return 0;
}
4.7.4 多态案例
案例一:制作计算器类

说明:分别利用普通写法和多态技术,设计实现两个操作数进行运算的计算器类

普通写法

#include <iostream>
#include <string>
using namespace std;class calculator
{
public:int operation(string oper){if (oper == "+"){return m_Num1 + m_Num2;}else if (oper == "-"){return m_Num1 - m_Num2;}else if (oper == "*"){return m_Num1 * m_Num2;}else if (oper == "/"){return m_Num1 / m_Num2;}else{return 0;}}int m_Num1;int m_Num2;
};void test01()
{calculator calc;calc.m_Num1 = 10;calc.m_Num2 = 20;cout << calc.m_Num1 << " + " << calc.m_Num2 << " = " << calc.operation("+") << endl;cout << calc.m_Num1 << " - " << calc.m_Num2 << " = " << calc.operation("-") << endl;cout << calc.m_Num1 << " * " << calc.m_Num2 << " = " << calc.operation("*") << endl;cout << calc.m_Num1 << " / " << calc.m_Num2 << " = " << calc.operation("/") << endl;}int main()
{test01();system("pause");return 0;
}

多态技术

#include <iostream>
using namespace std;class AbstractCalculator //定义一个计算器抽象类
{
public:virtual int operation(){return 0;}int m_Num1;int m_Num2;
};class AddCalculator :public AbstractCalculator  //加法
{int operation(){return m_Num1 + m_Num2;}
};class SubCalculator :public AbstractCalculator  //减法
{int operation(){return m_Num1 - m_Num2;}
};class MulCalculator :public AbstractCalculator   //乘法
{int operation(){return m_Num1 * m_Num2;}
};class divCalculator :public AbstractCalculator   //除法
{ int operation(){return m_Num1 / m_Num2;}
};void test01()
{//加法AbstractCalculator* calc = new AddCalculator;calc->m_Num1 = 10;calc ->m_Num2 = 20;cout << calc->m_Num1 << " + " << calc->m_Num2 << " = " << calc->operation() << endl; //30delete calc; //用完销毁//减法calc = new SubCalculator;calc->m_Num1 = 10;calc->m_Num2 = 20;cout << calc->m_Num1 << " - " << calc->m_Num2 << " = " << calc->operation() << endl; //-10delete calc; //用完销毁//乘法calc = new MulCalculator;calc->m_Num1 = 10;calc->m_Num2 = 20;cout << calc->m_Num1 << " * " << calc->m_Num2 << " = " << calc->operation() << endl; //200delete calc; //用完销毁//除法calc = new divCalculator;calc->m_Num1 = 10;calc->m_Num2 = 20;cout << calc->m_Num1 << " / " << calc->m_Num2 << " = " << calc->operation() << endl; //0delete calc; //用完销毁}int main()
{test01();system("pause");return 0;
}
案例二:制作冷饮

大致流程:煮水 - 冲泡 - 倒杯 - 加料 - 加冰

利用多态技术实现,提供抽象制作冷饮基类,提供子类制作拿铁和柠檬红茶

#include <iostream>
using namespace std;class AbstractDrinking
{
public:virtual void Boil() = 0;      //煮水virtual void Brew() = 0;      //冲泡virtual void PoutGlass() = 0; //倒杯virtual void Feeding() = 0;   //加料virtual void AddIce() = 0;    //加冰void MakeDrink()              //制作冷饮{Boil();Brew();PoutGlass();Feeding();AddIce();}
};class Latte :public AbstractDrinking //拿铁
{
public:virtual void Boil() //煮水{cout << "煮矿泉水" << endl;}virtual void Brew() //冲泡{cout << "冲泡咖啡粉" << endl;}virtual void PoutGlass() //倒杯{cout << "倒入玻璃杯中" << endl;}virtual void Feeding() //加料{cout << "加入牛奶" << endl;}virtual void AddIce()  //加冰{cout << "加入冰块" << endl;}
};class LemonBlackTea :public AbstractDrinking //柠檬红茶
{
public:virtual void Boil() //煮水{cout << "煮矿泉水" << endl;}virtual void Brew() //冲泡{cout << "冲泡红茶" << endl;}virtual void PoutGlass() //倒杯{cout << "倒入玻璃杯中" << endl;}virtual void Feeding() //加料{cout << "加入柠檬片" << endl;}virtual void AddIce()  //加冰{cout << "加入冰块" << endl;}
};void doWork(AbstractDrinking* abd) //制作函数
{abd->MakeDrink();delete abd;
}void test01()
{doWork(new LemonBlackTea);cout << "-----------" << endl;doWork(new Latte);
}int main()
{test01();system("pause");return 0;
}
案例三:组装电脑

说明:电脑主要组成部件、CPU、内存条、显示器。

  • 将每个零件封装出抽象类,并提供不同厂商生产不同的零件,如Intel厂商和Nvidia厂商

  • 创建电脑类提供让电脑工作的函数,并调用每个零件工作接口

  • 测试时组装三台不同的电脑进行工作

#include <iostream>
using namespace std;class CPU
{
public:virtual void calculate() = 0;
};class Memory
{
public:virtual void storage() = 0;
};class Display
{
public:virtual void display() = 0;
};class Computer
{
public:Computer(CPU* cpu, Memory* mem, Display* dp){m_cpu = cpu;m_mem = mem;m_dp = dp;}void doWork() //工作函数{//调用接口m_cpu->calculate();m_mem->storage();m_dp->display();}~Computer(){if (m_cpu != NULL) //释放CPU部件{delete m_cpu;m_cpu = NULL;}if (m_dp != NULL)  //释放显示器部件{delete m_cpu;m_dp = NULL;}if (m_mem != NULL)  //释放内存条部件{delete m_cpu;m_mem = NULL;}}
private:CPU* m_cpu;    Memory* m_mem;Display* m_dp;};class IntelCPU :public CPU
{
public:virtual void calculate(){cout << "Intel - CPU 开始运行工作" << endl;}
};class NvidiaCPU :public CPU
{
public:virtual void calculate(){cout << "Nvidia - CPU 开始运行工作" << endl;}
};class IntelDisplay :public Display
{
public:virtual void display(){cout << "Intel - 显示器 开始显示工作" << endl;}
};class NvidiaDisplay :public Display
{
public:virtual void display(){cout << "Nvidia - 显示器 开始显示工作" << endl;}
};class IntelMemory :public Memory
{
public:virtual void storage(){cout << "Intel - 内存条 开始存储工作" << endl;}
};class NvidiaMemory :public Memory
{
public:virtual void storage(){cout << "Nvidia - 内存条 开始存储工作" << endl;}
};void test01()
{//第一台电脑零部件/*CPU* intelCpu = new IntelCPU;Display* intelDisplay = new IntelDisplay;Memory* intelMem = new IntelMemory;*///创建第一台电脑cout << "第一台电脑开始工作" << endl;Computer* computer1 = new Computer(new IntelCPU, new IntelMemory, new IntelDisplay);  //第一台电脑computer1->doWork();delete computer1;cout << "----------------------" << endl;cout << "第二台电脑开始工作" << endl;Computer* computer2 = new Computer(new NvidiaCPU,new NvidiaMemory, new NvidiaDisplay);//第二胎电脑computer2->doWork();delete computer2;cout << "----------------------" << endl;cout << "第三台电脑开始工作" << endl;Computer* computer3 = new Computer(new IntelCPU, new NvidiaMemory, new IntelDisplay); //第三台电脑computer3->doWork();delete computer3;
}int main()
{test01();system("pause");return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

树莓派部署yolov5实现目标检测(ubuntu22.04.3)

最近两天搞了一下树莓派部署yolov5&#xff0c;有点难搞&#xff08;这个东西有点老&#xff0c;版本冲突有些包废弃了等等&#xff09; 最后换到ubuntu系统弄了&#xff0c;下面是我的整体步骤&#xff08;建议先使能一下ssh&#xff08;最下面有&#xff09;&#xff0c;结合…

Redis中的复制功能(五)

心跳检测 概述 在命令传播阶段&#xff0c;从服务器默认会以每秒一次的频率&#xff0c;向主服务器发送命令: REPLCONF ACK < replication_offset >其中replication_offset是从服务器当前的复制偏移量。 发送REPLCONF ACK命令对于主从服务器有三个作用: 1.检测主从服…

一、企业级架构之LNMP

一、LNMP 概述 1、LNMP之间的关系&#xff1a; LNMP Linux Nginx MySQL PHP 2、配置LNMP服务器&#xff1a; (1) 克隆一台centos7虚拟机&#xff0c;修改 IP 地址 和 UUID 编号。 IP 为 10.1.1.10&#xff0c;UUID 修改后三位。 (2) 设置主机名称&#xff0c;绑定IP地…

GraalVM运行模式和企业级应用

文章目录 GraalVM运行模式JIT模式AOT模式 GraalVM的问题和解决方案GraalVM企业级应用传统架构的问题Serverless架构函数计算Serverless应用场景Serverless应用 GraalVM内存参数 GraalVM运行模式 JIT模式 JIT&#xff08; Just-In-Time &#xff09;模式 &#xff0c;即时编译模…

SCI一区 | Matlab实现NGO-TCN-BiGRU-Attention北方苍鹰算法优化时间卷积双向门控循环单元融合注意力机制多变量时间序列预测

SCI一区 | Matlab实现NGO-TCN-BiGRU-Attention北方苍鹰算法优化时间卷积双向门控循环单元融合注意力机制多变量时间序列预测 目录 SCI一区 | Matlab实现NGO-TCN-BiGRU-Attention北方苍鹰算法优化时间卷积双向门控循环单元融合注意力机制多变量时间序列预测预测效果基本介绍模型…

【Linux】SSH协议应用

SSH协议 SSH简介实现OpenSSH ssh中的四个文件~/.ssh文件路径实验解析 SSH 简介 SSH&#xff08;secure shell&#xff09;只是一种协议&#xff0c;存在多种实现&#xff0c;既有商业实现&#xff0c;也有开源实现。本文针对的实现是OpenSSH&#xff0c;它是自由软件&#xf…

2024年腾讯云4核8G服务器性能可以满足哪些使用场景?

腾讯云4核8G服务器多少钱&#xff1f;腾讯云4核8G轻量应用服务器12M带宽租用价格646元15个月&#xff0c;活动页面 txybk.com/go/txy 活动链接打开如下图所示&#xff1a; 腾讯云4核8G服务器优惠价格 这台4核8G服务器是轻量应用服务器&#xff0c;详细配置为&#xff1a;轻量4核…

运筹学经典问题(八):CVRP和VRP-TW

文章目录 问题描述问题建模决策变量数学建模基于容量的消除子环的约束 &#xff08;load-based SECs&#xff09; CVRP完整的数学模型加上时间窗限制的CVRP 问题描述 给定一个图&#xff0c;图上的点代表客户&#xff0c;边代表客户之间的路线&#xff0c;边的权重代表客户之间…

学透Spring Boot — 004. Spring Boot Starter机制和自动配置机制

如果你项目中一直用的是 Spring Boot&#xff0c;那么恭喜你没有经历过用 Spring 手动集成其它框架的痛苦。 都说 Spring Boot 大大简化了 Spring 框架开发 Web 应用的难度&#xff0c;这里我们通过配置 Hibernate 的两种方式来深刻体会这一点&#xff1a; 使用 Spring 框架集…

MySQL 导入库/建表时/出现乱码

问题描述&#xff1a; 新建不久的项目在使用Navicat for MySQL进行查看数据&#xff0c;发现表中注释的部分乱码&#xff0c;但是项目中获取的数据使用不会。 猜测因为是数据库编码和项目中使用的不一样&#xff0c;又因为项目的连接语句定义了需要编码&#xff0c;故项目运行…

基于向量数据库搭建自己的搜索引擎

前言【基于chatbot】 厌倦了商业搜索引擎搜索引擎没完没了的广告&#xff0c;很多时候&#xff0c;只是需要精准高效地检索信息&#xff0c;而不是和商业广告“斗智斗勇”。以前主要是借助爬虫工具&#xff0c;而随着技术的进步&#xff0c;现在有了更多更方便的解决方案&…

AcWing-游戏

1388. 游戏 - AcWing题库 所需知识&#xff1a;博弈论&#xff0c;区间dp 由于双方都采取最优的策略来取数字&#xff0c;所以结果为确定的&#xff0c;有可能会有多个不同的过程&#xff0c;但是我们只需要关注最终结果就行了。 方法一&#xff1a; 定义dp[i][j] 表示区间…

Loss【1】:Focal Loss

系列文章目录 文章目录 系列文章目录前言1. 什么是 Focal Loss2. 逐过程解析 Focal Loss3. Focal Loss 的 PyTorch 实现总结 前言 类别不平衡是一个在目标检测领域被广泛讨论的问题&#xff0c;因为目标数量的多少在数据集中能很直观的体现。同时&#xff0c;在分割中这也是一…

理解pytorch的广播语义

目录 什么是广播运算 广播的条件 示例 示例1 示例2 示例3 补1 示例4 原位运算 示例5 参与广播运算的两个tensor&#xff0c;必须是从右向左对齐 总结规律 两个tensor可以做广播运算的条件&#xff1a; 两个可以互相广播的tensor运算的步骤&#xff1a; 例子&#x…

[C#]OpenCvSharp改变图像的对比度和亮度

目的 访问像素值mat.At<T>(y,x) 用0初始化矩阵Mat.Zeros 饱和操作SaturateCast.ToByte 亮度和对比度调整 g(x)αf(x)β 用α(>0)和β一般称作增益(gain)和偏置(bias)&#xff0c;分别控制对比度和亮度 把f(x)看成源图像像素&#xff0c;把g(x)看成输出图像像素…

蓝桥杯—DS1302

目录 1.管脚 2.时序&官方提供的读写函数 3.如何使用读写函数 4.如何在数码管中显示在DS1302中读取出的数据&#xff1f; 1.管脚 2.时序&官方提供的读写函数 /* # DS1302代码片段说明1. 本文件夹中提供的驱动代码供参赛选手完成程序设计参考。2. 参赛选手可以自行…

如何锁定鼠标光标在水平、垂直或45度对角线模式下移动 - 鼠标水平垂直移动锁定器简易教程

在我们进行精细工作例如如创建图标和图形设计时&#xff0c;通常需要我们对鼠标移动进行精确控制。一旦向左或向右轻微移动&#xff0c;都可能导致设计出错。若出现不必要的错误&#xff0c;我们极有可能不得不重新开始&#xff0c;这会令人感到非常沮丧。这种情况下&#xff0…

RabbitMQ3.x之九_Docker中安装RabbitMQ

RabbitMQ3.x之_Docker中安装RabbitMQ 文章目录 RabbitMQ3.x之_Docker中安装RabbitMQ1. 官网2. 安装1 .拉取镜像2. 运行容器 3. 访问 1. 官网 rabbitmq - Official Image | Docker Hub 2. 安装 1 .拉取镜像 docker pull rabbitmq:3.13.0-management2. 运行容器 # latest Rabb…

单元测试 mockito(二)

1.返回指定值 2.void返回值指定插桩 3.插桩的两种方式 when(obj.someMethod()).thenXxx():其中obj可以是mock对象 doXxx().wien(obj).someMethod():其中obj可以是mock/spy对象 spy对象在没有插桩时是调用真实方法的,写在when中会导致先执行一次原方法,达不到mock的目的&#x…

模块化编程:AMD 和 CMD 的魅力

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…