网站背景自动切换crm系统是什么意思

当前位置: 首页 > news >正文

网站背景自动切换,crm系统是什么意思,网站建设及维护流程,泰安百度推广代理​ ​#x1f4dd;个人主页#xff1a;Sherry的成长之路 #x1f3e0;学习社区#xff1a;Sherry的成长之路#xff08;个人社区#xff09; #x1f4d6;专栏链接#xff1a;C学习 #x1f3af;长路漫漫浩浩#xff0c;万事皆有期待 上一篇博客#xff1a;【C】STL…​ ​个人主页Sherry的成长之路 学习社区Sherry的成长之路个人社区 专栏链接C学习 长路漫漫浩浩万事皆有期待 上一篇博客【C】STL详解八—— priority_queue的使用及模拟实现仿函数 文章目录 继承的概念及定义继承的概念继承的定义定义格式继承方式和访问限定符继承基类成员访问方式的变化默认继承方式 基类和派生类对象赋值转换继承中的作用域派生类的默认成员函数继承与友元继承的方式菱形虚拟继承菱形虚拟继承原理继承的总结和反思相关笔试面试题 总结 继承的概念及定义 继承的概念 继承inheritance机制是面向对象程序设计使代码可以复用的重要的手段它允许程序员在保持原有类特性的基础上进行扩展增加功能这样产生新的类称为派生类。 继承呈现了面向对象程序设计的层次结构体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用而继承便是类设计层次的复用。 例如以下代码中Student类和Teacher类就继承了Person类。 //父类 class Person { public:void Print(){cout name: _name endl;cout age: _age endl;} protected:string _name 张三; //姓名int _age 18; //年龄 }; //子类 class Student : public Person { protected:int _stuid; //学号 }; //子类 class Teacher : public Person { protected:int _jobid; //工号 };继承后父类Person的成员包括成员函数和成员变量都会变成子类的一部分也就是说子类Student和Teacher复用了父类Person的成员。 继承的定义 定义格式 继承的定义格式如下
说明 在继承当中父类也称为基类子类是由基类派生而来的所以子类又称为派生类。 继承方式和访问限定符 我们知道访问限定符有以下三种 1.public访问 2.protected访问 3.private访问 而继承的方式也有类似的三种 1.public继承 2.protected继承 3.private继承 继承基类成员访问方式的变化 基类当中被不同访问限定符修饰的成员以不同的继承方式继承到派生类当中后该成员最终在派生类当中的访问方式将会发生变化。
稍作观察实际上基类成员访问方式的变化规则也不是无迹可寻的我们可以认为三种访问限定符的权限大小为public protected private基类成员访问方式的变化规则如下 1.在基类当中的访问方式为public或protected的成员在派生类当中的访问方式变为Min(成员在基类的访问方式继承方式)。 2.在基类当中的访问方式为private的成员在派生类当中都是不可见的。 基类的private成员在派生类当中不可见是什么意思 这句话的意思是我们无法在派生类当中访问基类的private成员。例如虽然Student类继承了Person类但是我们无法在Student类当中访问Person类当中的private成员_name。 //基类 class Person { private:string _name 张三; //姓名 }; //派生类 class Student : public Person { public:void Print(){//在派生类当中访问基类的private成员error!cout _name endl; } protected:int _stuid; //学号 };也就是说基类的private成员无论以什么方式继承在派生类中都是不可见的这里的不可见是指基类的私有成员虽然被继承到了派生类对象中但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它。 因此基类的private成员在派生类中是不能被访问的如果基类成员不想在类外直接被访问但需要在派生类中能访问就需要定义为protected由此可以看出protected限定符是因继承才出现的。 注意 在实际运用中一般使用的都是public继承几乎很少使用protected和private继承也不提倡使用protected和private继承因为使用protected和private继承下来的成员都只能在派生类的类里面使用实际中扩展维护性不强。 默认继承方式 在使用继承的时候也可以不指定继承方式使用关键字class时默认的继承方式是private使用struct时默认的继承方式是public。 例如在关键字为class的派生类当中所继承的基类成员_name的访问方式变为private。 //基类 class Person { public:string _name 张三; //姓名 }; //派生类 class Student : Person //默认为private继承 { protected:int _stuid; //学号 };而在关键字为struct的派生类当中所继承的基类成员_name的访问方式仍为public。 //基类 class Person { public:string _name 张三; //姓名 }; //派生类 struct Student : Person //默认为public继承 { protected:int _stuid; //学号 };注意 虽然继承时可以不指定继承方式而采用默认的继承方式但还是最好显示的写出继承方式。 基类和派生类对象赋值转换 派生类对象可以赋值给基类的对象、基类的指针以及基类的引用因为在这个过程中会发生基类和派生类对象之间的赋值转换。 例如对于以下基类及其派生类。 //基类 class Person { protected:string _name; //姓名string _sex; //性别int _age; //年龄 }; //派生类 class Student : public Person { protected:int _stuid; //学号 };代码当中可以出现以下逻辑 Student s; Person p s; //派生类对象赋值给基类对象 Person* ptr s; //派生类对象赋值给基类指针 Person ref s; //派生类对象赋值给基类引用对于这种做法有个形象的说法叫做切片/切割寓意把派生类中基类那部分切来赋值过去。 派生类对象赋值给基类对象图示
派生类对象赋值给基类指针图示
派生类对象赋值给基类引用图示
注意 基类对象不能赋值给派生类对象基类的指针可以通过强制类型转换赋值给派生类的指针但是此时基类的指针必须是指向派生类的对象才是安全的。 继承中的作用域 在继承体系中的基类和派生类都有独立的作用域。若子类和父类中有同名成员子类成员将屏蔽父类对同名成员的直接访问这种情况叫隐藏也叫重定义。 例如对于以下代码访问成员_num时将访问到子类当中的_num。 #include iostream #include string using namespace std; //父类 class Person { protected:int _num 111; }; //子类 class Student : public Person { public:void fun(){cout _num endl;} protected:int _num 999; }; int main() {Student s;s.fun(); //999return 0; }若此时我们就是要访问父类当中的_num成员我们可以使用作用域限定符进行指定访问。 void fun() {cout Person::_num endl; //指定访问父类当中的_num成员 }需要注意的是如果是成员函数的隐藏只需要函数名相同就构成隐藏。 例如对于以下代码调用成员函数fun时将直接调用子类当中的fun若想调用父类当中的fun则需使用作用域限定符指定类域。 #include iostream #include string using namespace std; //父类 class Person { public:void fun(int x){cout x endl;} }; //子类 class Student : public Person { public:void fun(double x){cout x endl;} }; int main() {Student s;s.fun(3.14); //直接调用子类当中的成员函数funs.Person::fun(20); //指定调用父类当中的成员函数funreturn 0; }特别注意 代码当中父类中的fun和子类中的fun不是构成函数重载因为函数重载要求两个函数在同一作用域而此时这两个fun函数并不在同一作用域。为了避免类似问题实际在继承体系当中最好不要定义同名的成员。因此为隐藏关系。 派生类的默认成员函数 默认成员函数即我们不写编译器会自动生成的函数类当中的默认成员函数有以下六个
下面我们看看派生类当中的默认成员函数与普通类的默认成员函数的不同之处。 例如我们以下面这个Person类为基类。 //基类 class Person { public://构造函数Person(const string name peter):_name(name){cout Person() endl;}//拷贝构造函数Person(const Person p):_name(p._name){cout Person(const Person p) endl;}//赋值运算符重载函数Person operator(const Person p){cout Person operator(const Person p) endl;if (this ! p){_name p._name;}return *this;}//析构函数~Person(){cout ~Person() endl;} private:string _name; //姓名 };我们用该基类派生出Student类Student类当中的默认成员函数的基本逻辑如下 //派生类 class Student : public Person { public://构造函数Student(const string name, int id):Person(name) //调用基类的构造函数初始化基类的那一部分成员, _id(id) //初始化派生类的成员{cout Student() endl;}//拷贝构造函数Student(const Student s):Person(s) //调用基类的拷贝构造函数完成基类成员的拷贝构造, _id(s._id) //拷贝构造派生类的成员{cout Student(const Student s) endl;}//赋值运算符重载函数Student operator(const Student s){cout Student operator(const Student s) endl;if (this ! s){Person::operator(s); //调用基类的operator完成基类成员的赋值_id s._id; //完成派生类成员的赋值}return *this;}//析构函数~Student(){cout Student() endl;//派生类的析构函数会在被调用完成后自动调用基类的析构函数} private:int _id; //学号 };派生类与普通类的默认成员函数的不同之处概括为以下几点 派生类的构造函数被调用时会自动调用基类的构造函数初始化基类的那一部分成员如果基类当中没有默认的构造函数则必须在派生类构造函数的初始化列表当中显示调用基类的构造函数。 派生类的拷贝构造函数必须调用基类的拷贝构造函数完成基类成员的拷贝构造。 派生类的赋值运算符重载函数必须调用基类的赋值运算符重载函数完成基类成员的赋值。 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。 派生类对象初始化时会先调用基类的构造函数再调用派生类的构造函数。 派生类对象在析构时会先调用派生类的析构函数再调用基类的析构函数。 在编写派生类的默认成员函数时需要注意以下几点 派生类和基类的赋值运算符重载函数因为函数名相同构成隐藏因此在派生类当中调用基类的赋值运算符重载函数时需要使用作用域限定符进行指定调用。 由于多态的某些原因任何类的析构函数名都会被统一处理为destructor();。因此派生类和基类的析构函数也会因为函数名相同构成隐藏若是我们需要在某处调用基类的析构函数那么就要使用作用域限定符进行指定调用。 在派生类的拷贝构造函数和operator当中调用基类的拷贝构造函数和operator的传参方式是一个切片行为都是将派生类对象直接赋值给基类的引用。 说明一下 基类的构造函数、拷贝构造函数、赋值运算符重载函数我们都可以在派生类当中自行进行调用而基类的析构函数是当派生类的析构函数被调用后由编译器自动调用的我们若是自行调用基类的构造函数就会导致基类被析构多次的问题。 我们知道创建派生类对象时是先创建的基类成员再创建的派生类成员编译器为了保证析构时先析构派生类成员再析构基类成员的顺序析构所以编译器会在派生类的析构函数被调用后自动调用基类的析构函数。
继承与友元 友元关系不能继承也就是说基类的友元可以访问基类的私有和保护成员但是不能访问派生类的私有和保护成员。 例如以下代码中Display函数是基类Person的友元当时Display函数不是派生类Student的友元即Display函数无法访问派生类Student当中的私有和保护成员。 #include iostream #include string using namespace std; class Student; class Person { public://声明Display是Person的友元friend void Display(const Person p, const Student s); protected:string _name; //姓名 }; class Student : public Person { protected:int _id; //学号 }; void Display(const Person p, const Student s) {cout p._name endl; //可以访问cout s._id endl; //无法访问 } int main() {Person p;Student s;Display(p, s);return 0; }此时我们也可以通过打印Person类和Student类当中静态成员_count的地址来证明它们就是同一个变量。 cout Person::_count endl; //00F1F320 cout Student::_count endl; //00F1F320比如下面这道题: 分析如下
所以C正确 继承的方式 单继承一个子类只有一个直接父类时称这个继承关系为单继承。
多继承一个子类有两个或两个以上直接父类时称这个继承关系为多继承。
菱形继承菱形继承是多继承的一种特殊情况。
从菱形继承的模型构造就可以看出菱形继承的继承方式存在数据冗余和二义性的问题。 例如对于以上菱形继承的模型当我们实例化出一个Assistant对象后访问成员时就会出现二义性问题。 #include iostream #include string using namespace std; class Person { public:string _name; //姓名 }; class Student : public Person { protected:int _num; //学号 }; class Teacher : public Person { protected:int _id; //职工编号 }; class Assistant : public Student, public Teacher { protected:string _majorCourse; //主修课程 }; int main() {Assistant a;a._name peter; //二义性无法明确知道要访问哪一个_namereturn 0; }Assistant对象是多继承的Student和Teacher而Student和Teacher当中都继承了Person因此Student和Teacher当中都有_name成员若是直接访问Assistant对象的_name成员会出现访问不明确的报错。
对于此我们可以显示指定访问Assistant哪个父类的_name成员。 //显示指定访问哪个父类的成员 a.Student::_name 张同学; a.Teacher::_name 张老师;虽然该方法可以解决二义性的问题但仍然不能解决数据冗余的问题。因为在Assistant的对象在Person成员始终会存在两份。 菱形虚拟继承 为了解决菱形继承的二义性和数据冗余问题出现了虚拟继承。如前面说到的菱形继承关系在Student和Teacher继承Person是使用虚拟继承即可解决问题。
虚拟继承代码如下 #include iostream #include string using namespace std; class Person { public:string _name; //姓名 }; class Student : virtual public Person //虚拟继承 { protected:int _num; //学号 }; class Teacher : virtual public Person //虚拟继承 { protected:int _id; //职工编号 }; class Assistant : public Student, public Teacher { protected:string _majorCourse; //主修课程 }; int main() {Assistant a;a._name peter; //无二义性return 0; }此时就可以直接访问Assistant对象的_name成员了并且之后就算我们指定访问Assistant的Student父类和Teacher父类的_name成员访问到的都是同一个结果解决了二义性的问题。 cout a.Student::_name endl; //peter cout a.Teacher::_name endl; //peter而我们打印Assistant的Student父类和Teacher父类的_name成员的地址时显示的也是同一个地址解决了数据冗余的问题。 cout a.Student::_name endl; //0136F74C cout a.Teacher::_name endl; //0136F74C菱形虚拟继承原理 在此之前我们先看看不使用菱形虚拟继承时以下菱形继承当中D类对象的各个成员在内存当中的分布情况。 测试代码如下 #include iostream using namespace std; class A { public:int _a; }; class B : public A { public:int _b; }; class C : public A { public:int _c; }; class D : public B, public C { public:int _d; }; int main() {D d;d.B::_a 1;d.C::_a 2;d._b 3;d._c 4;d._d 5;return 0; }通过内存窗口我们可以看到D类对象当中各个成员在内存当中的分布情况如下
也就是说D类对象当中各个成员在内存当中的分布情况如下
这里就可以看出为什么菱形继承导致了数据冗余和二义性根本原因就是D类对象当中含有两个_a成员。 现在我们再来看看使用菱形虚拟继承时以下菱形继承当中D类对象的各个成员在内存当中的分布情况。
测试代码如下 #include iostream using namespace std; class A { public:int _a; }; class B : virtual public A { public:int _b; }; class C : virtual public A { public:int _c; }; class D : public B, public C { public:int _d; }; int main() {D d;d.B::_a 1;d.C::_a 2;d._b 3;d._c 4;d._d 5;return 0; }通过内存窗口我们可以看到D类对象当中各个成员在内存当中的分布情况如下
其中D类对象当中的_a成员被放到了最后而在原来存放两个_a成员的位置变成了两个指针这两个指针叫虚基表指针它们分别指向一个虚基表。
虚基表中包含两个数据第一个数据是为多态的虚表预留的存偏移量的位置这里我们不必关心第二个数据就是当前类对象位置距离公共虚基类的偏移量。 也就是说这两个指针经过一系列的计算最终都可以找到成员_a。
我们若是将D类对象赋值给B类对象在这个切片过程中就需要通过虚基表中的第二个数据找到公共虚基类A的成员得到切片后该B类对象在内存中仍然保持这种分布情况。 D d; B b d; //切片行为得到切片后该B类对象当中各个成员在内存当中的分布情况如下
其中_a对象仍然存储在该B类对象的最后。 继承的总结和反思 1.很多人都说C语法复杂其实多继承就是一个体现。有了多继承就可能存在菱形继承有了菱形继承就有菱形虚拟继承底层实现就很复杂。所以一般不建议设计出菱形继承否则代码在复杂度及性能上都容易出现问题当菱形继承出问题时难以分析并且会有一定的效率影响。 2.多继承可以认为是C的缺陷之一很多后来的OO(Object Oriented)语言都没有多继承如Java。 继承和组合 继承是一种is-a的关系也就是说每个派生类对象都是一个基类对象而组合是一种has-a的关系若是B组合了A那么每个B对象中都有一个A对象。 例如车类和宝马类就是is-a的关系它们之间适合使用继承。 class Car { protected:string _colour; //颜色string _num; //车牌号 }; class BMW : public Car { public:void Drive(){cout this is BMW endl;} };而车和轮胎之间就是has-a的关系它们之间则适合使用组合。 class Tire { protected:string _brand; //品牌size_t _size; //尺寸 }; class Car { protected:string _colour; //颜色string _num; //车牌号Tire _t; //轮胎 };若是两个类之间既可以看作is-a的关系又可以看作has-a的关系则优先使用组合。 原因如下 继承允许你根据基类的实现来定义派生类的实现这种通过生成派生类的复用通常被称为白箱复用(White-box reuse)。术语“白箱”是相对可视性而言在继承方式中基类的内部细节对于派生类可见继承一定程度破坏了基类的封装基类的改变对派生类有很大的影响派生类和基类间的依赖性关系很强耦合度高。 组合是类继承之外的另一种复用选择新的更复杂的功能可以通过组装或组合对象来获得。对象组合要求被组合的对象具有良好定义的接口这种复用风格被称之为黑箱复用(Black-box reuse)因为对象的内部细节是不可见的对象只以“黑箱”的形式出现组合类之间没有很强的依赖关系耦合度低优先使用对象组合有助于你保持每个类被封装。 实际中尽量多使用组合组合的耦合度低代码维护性好。不过继承也是有用武之地的有些关系就适合用继承另外要实现多态也必须要继承。若是类之间的关系既可以用继承又可以用组合则优先使用组合。 相关笔试面试题 什么是菱形继承菱形继承的问题是什么 菱形继承是多继承的一种特殊情况两个子类继承同一个父类而又有子类同时继承这两个子类我们称这种继承为菱形继承。 菱形继承因为子类对象当中会有两份父类的成员因此会导致数据冗余和二义性的问题。 什么是菱形虚拟继承如何解决数据冗余和二义性 菱形虚拟继承是指在菱形继承的腰部使用虚拟继承(virtual)的继承方式菱形虚拟继承对于D类对象当中重复的A类成员只存储一份然后采用虚基表指针和虚基表使得D类对象当中继承的B类和C类可以找到自己继承的A类成员从而解决了数据冗余和二义性的问题。 继承和组合的区别什么时候用继承什么时候用组合 继承是一种is-a的关系而组合是一种has-a的关系。如果两个类之间是is-a的关系使用继承如果两个类之间是has-a的关系则使用组合如果两个类之间的关系既可以看作is-a的关系又可以看作has-a的关系则优先使用组合。 总结 今天我们学习了C继承中切片、隐藏、默认成员函数、菱形的相关知识了解了一些有关的底层原理。接下来我们将进行C中多态的学习。希望我的文章和讲解能对大家的学习提供一些帮助。 当然本文仍有许多不足之处欢迎各位小伙伴们随时私信交流、批评指正我们下期见