深圳比较好的建网站公司,做美食的网站有那一些,wordpress手机页面没有注册,海城整站优化一、模板进阶
1、非类型模板参数
模板参数分类类型形参与非类型形参。
类型形参即#xff1a;出现在模板参数列表中#xff0c;跟在class或者typename之类的参数类型名称。
非类型形参#xff0c;就是用一个常量作为类(函数)模板的一个参数#xff0c;在类(函数)模板中…一、模板进阶
1、非类型模板参数
模板参数分类类型形参与非类型形参。
类型形参即出现在模板参数列表中跟在class或者typename之类的参数类型名称。
非类型形参就是用一个常量作为类(函数)模板的一个参数在类(函数)模板中可将该参数当成常
量来使用。
namespace arr
{// 定义一个模板类型的静态数组templateclass T, size_t N 10class array{public:T operator[](size_t index) { return _array[index]; }const T operator[](size_t index)const { return _array[index]; }size_t size()const { return _size; }bool empty()const { return 0 _size; }private:T _array[N];size_t _size;};
}2、模板的特化
通常情况下使用模板可以实现一些与类型无关的代码但对于一些特殊类型的可能会得到一些
错误的结果需要特殊处理比如实现了一个专门用来进行小于比较的函数模板
class Date
{
public:Date(int year, int month, int day):_year(year){}bool operator(Date d){return _year d._year;}
private:int _year 0;
};
templateclass T
bool Less(T left, T right)
{return left right;
}
int main()
{cout Less(1, 2) endl; // 可以比较结果正确Date d1(2022, 7, 7);Date d2(2022, 7, 8);cout Less(d1, d2) endl; // 可以比较结果正确Date* p1 d1;Date* p2 d2;cout Less(p1, p2) endl; // 可以比较结果错误return 0;
}
可以看到Less绝对多数情况下都可以正常比较但是在特殊场景下就得到错误的结果。上述示
例中p1指向的d1显然小于p2指向的d2对象但是Less内部并没有比较p1和p2指向的对象内
容而比较的是p1和p2指针的地址这就无法达到预期而错误。
此时就需要对模板进行特化。即在原模板类的基础上针对特殊类型所进行特殊化的实现方
式。模板特化中分为函数模板特化与类模板特化。
函数模板的特化步骤 必须要先有一个基础的函数模板 关键字template后面接一对空的尖括号 函数名后跟一对尖括号尖括号中指定需要特化的类型 函数形参表: 必须要和模板函数的基础参数类型完全相同如果不同编译器可能会报一些奇
怪的错误。
templateclass T
bool Less(T left, T right)
{return left right;
}
// 对Less函数模板进行特化
template
bool LessDate*(Date* left, Date* right)
{return *left *right;
}
int main()
{cout Less(1, 2) endl;Date d1(2022, 7, 7);Date d2(2022, 7, 8);cout Less(d1, d2) endl;Date* p1 d1;Date* p2 d2;cout Less(p1, p2) endl; // 调用特化之后的版本而不走模板生成了return 0;
}注意一般情况下如果函数模板遇到不能处理或者处理有误的类型为了实现简单通常都是将该
函数直接给出
bool Less(Date* left, Date* right)
{return *left *right;
}该种实现简单明了代码的可读性高容易书写因为对于一些参数类型复杂的函数模板特化
时特别给出因此函数模板不建议特化。
3、类模板特化
1、全特化
全特化即是将模板参数列表中所有的参数都确定化。
templateclass T1, class T2
class Data
{
public:Data() { cout DataT1, T2 endl; }
private:T1 _d1;T2 _d2;
};
template
class Dataint, char
{
public:Data() { cout Dataint, char endl; }
private:int _d1;char _d2;
};
void TestVector()
{Dataint, int d1;Dataint, char d2;
}2、偏特化
偏特化任何针对模版参数进一步进行条件限制设计的特化版本。比如对于以下模板类
templateclass T1, class T2
class Data
{public:Data() {coutDataT1, T2 endl;}private:T1 _d1;T2 _d2;
};偏特化有以下两种表现方式
部分特化
将模板参数类表中的一部分参数特化。
// 将第二个参数特化为int
template class T1
class DataT1, int
{public:Data() {coutDataT1, int endl;}private:T1 _d1;int _d2;
};参数更进一步的限制
偏特化并不仅仅是指特化部分参数而是针对模板参数更进一步的条件限制所设计出来的一
个特化版本。
//两个参数偏特化为指针类型
template typename T1, typename T2
class Data T1*, T2*
{
public:Data() { cout DataT1*, T2* endl; }
private:T1 _d1;T2 _d2;
};
//两个参数偏特化为引用类型
template typename T1, typename T2
class Data T1, T2
{
public:Data(const T1 d1, const T2 d2): _d1(d1), _d2(d2){cout DataT1, T2 endl;}
private:const T1 _d1;const T2 _d2;
};
void test2()
{Datadouble, int d1; // 调用特化的int版本Dataint, double d2; // 调用基础的模板Dataint*, int* d3; // 调用特化的指针版本Dataint, int d4(1, 2); // 调用特化的指针版本
}二、继承
1、概念
继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段它允许我们在保持原有
类特性的基础上进行扩展增加方法(成员函数)和属性(成员变量)这样产生新的类称子类。继承呈
现了面向对象程序设计的层次结构体现了由简单到复杂的认知过程。以前我们接触的函数层次的复
用继承是类设计层次的复用。
下面我们看到没有继承之前我们设计了两个类Student和TeacherStudent和Teacher都有姓名/地址/
电话/年龄等成员变量都有identity身份认证的成员函数设计到两个类里面就是冗余的。当然他们
也有⼀些不同的成员变量和函数比如老师独有成员变量是职称学生的独有成员变量是学号学生
的独有成员函数是学习老师的独有成员函数是授课。
#includeiostream
#includestring
using namespace std;
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; // 职称
};
int main()
{return 0;
}下面我们公共的成员都放到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 Student : public Person
{
public:// 学习void study(){// ...}protected:int _stuid; // 学号
};
class Teacher : public Person
{
public:// 授课void teaching(){//...}
protected:string title; // 职称
};
int main()
{Student s;Teacher t;s.identity();t.identity();return 0;
}2、继承定义
a、定义格式
下面我们看到Person是父类也称作基类。Student是子类也称作派生类。(因为翻译的原因所以
既叫父类/子类也叫父类/子类) 3、继承父类成员访问方式的变化 父类private成员在子类中无论以什么方式继承都是不可见的。这里的不可见是指父类的私有成员还
是被继承到了子类对象中但是语法上限制子类对象不管在里面还是类外面都不能去访问它。
父类private成员在子类中是不能被访问如果父类成员不想在类外直接被访问但需要在子类中能
访问就定义为protected。可以看出保护成员限定符是因继承才出现的。
实际上面的表格我们进行⼀下总结会发现父类的私有成员在子类都是不可见。父类的其他成员在
子类的访问方式 Min(成员在父类的访问限定符继承方式)public protected private。
使用关键字class时默认的继承方式是private使用struct时默认的继承方式是public不过最好显
示的写出继承方式。
在实际运用中⼀般使用都是public继承几乎很少使用protetced/private继承也不提倡使用
protetced/private继承因为protetced/private继承下来的成员都只能在子类的类里面使用实际
中扩展维护性不强。
// 实例演⽰三种继承关系下父类成员的各类型成员访问关系的变化
class Person
{
public:void Print(){cout _name endl;}
protected:string _name; // 姓名
private:int _age; // 年龄
};
//class Student : protected Person
//class Student : private Person
class Student : public Person
{
protected:int _stunum; // 学号
};4、继承类模板
#includeiostream
#includevector
using namespace std;
templateclass T
class stack :public vectorT
{
public:void push(const T t){vectorT::push_back(t);}void pop(){vectorT::pop_back();}const T top(){return vectorT::back();}
};
int main()
{stackint st;st.push(1);st.push(1);st.push(1);st.push(1);for (auto it : st){cout it;}return 0;
}5、父类和子类对象赋值兼容转换
public继承的子类对象 可以赋值给 父类的对象 / 父类的指针 / 父类的引用。这里有个形象的说法叫
切片或者切割。寓意把子类中父类那部分切来赋值过去。
父类对象不能赋值给子类对象。
父类的指针或者引用可以通过强制类型转换赋值给子类的指针或者引用。但是必须是父类的指针是
指向子类对象时才是安全的。这里父类如果是多态类型可以使用RTTI(Run-Time Type
Information)的dynamic_cast 来进行识别后进行安全转换。 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;
}
6、继承中的作用域
a、隐藏规则 在继承体系中父类和子类都有独⽴的作用域。 子类和父类中有同名成员子类成员将屏蔽父类对同名成员的直接访问这种情况叫隐藏。在子类成员函数中可以使用父类::父类成员 显示访问 需要注意的是如果是成员函数的隐藏只需要函数名相同就构成隐藏。 注意在实际中在继承体系里面最好不要定义同名的成员。
// Student的_num和Person的_num构成隐藏关系可以看出这样代码虽然能跑但是⾮常容易混淆
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;
}7、子类的默认成员函数
a、四个常见的默认构造函数
子类的构造函数必须调用父类的构造函数初始化父类的那⼀部分成员。如果父类没有默认的构造函
数则必须在子类构造函数的初始化列表阶段显示调用。 子类的拷贝构造函数必须调用父类的拷贝构造完成父类的拷贝初始化。 子类的operator必须要调用父类的operator完成父类的复制。需要注意的是子类的operator隐
藏了父类的operator所以显示调用父类的operator需要指定父类作用域
子类的析构函数会在被调用完成后⾃动调用父类的析构函数清理父类成员。因为这样才能保证子类
对象先清理子类成员再清理父类成员的顺序。 子类对象初始化先调用父类构造再调子类构造。 子类对象析构清理先调用子类析构再调父类的析构。 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;}Person operator(const Person p){cout Person operator(const Person p) endl;if (this ! p)_name p._name;return *this;}~Person(){cout ~Person() endl;}
protected:string _name; // 姓名
};
class Student : public Person
{
public:Student(const char* name, int num): Person(name), _num(num){cout Student() endl;}Student(const Student s): Person(s), _num(s._num){cout Student(const Student s) endl;}Student operator (const Student s){cout Student operator (const Student s) endl;if (this ! s){// 构成隐藏所以需要显⽰调用Person::operator (s);_num s._num;}return *this;}~Student(){cout ~Student() endl;}
protected:int _num; //学号
};
int main()
{Student s1(jack, 18);Student s2(s1);Student s3(rose, 17);s1 s3;return 0;
}8、继承与友元
友元关系不能继承也就是说父类友元不能访问子类私有和保护成员 。
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;// 编译报错error C2248: “Student::_stuNum”: ⽆法访问 protected 成员// 解决⽅案Display也变成Student 的友元即可Display(p, s);return 0;
}9、继承与静态成员
父类定义了static静态成员则整个继承体系里面只有⼀个这样的成员。无论派生出多少个子类都只
有⼀个static成员实例。
class Person
{
public:string _name;static int _count;
};
int Person::_count 0;
class Student : public Person
{
protected:int _stuNum;
};
int main()
{Person p;Student s;// 这⾥的运⾏结果可以看到⾮静态成员_name的地址是不⼀样的// 说明子类继承下来了父子类对象各有⼀份cout p._name endl;cout s._name endl;// 这⾥的运⾏结果可以看到静态成员_count的地址是⼀样的// 说明子类和父类共用同⼀份静态成员cout p._count endl;cout s._count endl;// 公有的情况下父子类指定类域都可以访问静态成员cout Person::_count endl;cout Student::_count endl;return 0;
}10、继承模型
单继承⼀个子类只有⼀个直接父类时称这个继承关系为单继承
多继承⼀个子类有两个或以上直接父类时称这个继承关系为多继承多继承对象在内存中的模型
是先继承的父类在前面后面继承的父类在后面子类成员在放到最后面。
菱形继承菱形继承是多继承的⼀种特殊情况。菱形继承的问题从下面的对象成员模型构造可以
看出菱形继承有数据冗余和⼆义性的问题在Assistant的对象中Person成员会有两份。⽀持多继承就
⼀定会有菱形继承像Java就直接不⽀持多继承规避掉了这⾥的问题所以实践中我们也是不建议
设计出菱形继承这样的模型的。 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()
{// 编译报错error C2385: 对“_name”的访问不明确Assistant a;a._name peter;// 需要显⽰指定访问哪个父类的成员可以解决⼆义性问题但是数据冗余问题⽆法解决a.Student::_name xxx;a.Teacher::_name yyy;return 0;
}11、虚继承
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;a.Student::_name xxx;a.Teacher::_name yyy;return 0;
}12、继承与组合
• public继承是⼀种is-a的关系。也就是说每个子类对象都是⼀个父类对象。
• 组合是⼀种has-a的关系。假设B组合了A每个B对象中都有⼀个A对象。
• 继承允许你根据父类的实现来定义子类的实现。这种通过⽣成子类的复用通常被称为白箱复用
(white-box reuse)。术语“白箱”是相对可视性而言在继承方式中父类的内部细节对子类可见
。继承⼀定程度破坏了父类的封装父类的改变对子类有很大的影响。子类和父类间的依赖关系
很强耦合度高。
• 对象组合是类继承之外的另⼀种复用选择。新的更复杂的功能可以通过组装或组合对象来获得。对
象组合要求被组合的对象具有良好定义的接⼝。这种复用风格被称为黑箱复用(black-box reuse)
因为对象的内部细节是不可见的。对象只以“黑箱”的形式出现。 组合类之间没有很强的依赖关
系耦合度低。优先使用对象组合有助于你保持每个类被封装。
• 优先使用组合而不是继承。实际尽量多去用组合组合的耦合度低代码维护性好。不过也不太
那么绝对类之间的关系就适合继承(is-a)那就用继承另外要实现多态也必须要继承。类之间的
关系既适合用继承(is-a)也适合组合(has-a)就用组合。
• 很多⼈说C语法复杂其实多继承就是⼀个体现。有了多继承就存在菱形继承有了菱形继承
就有菱形虚拟继承底层实现就很复杂性能也会有⼀些损失所以最好不要设计出菱形继承。
12、继承与组合
• public继承是⼀种is-a的关系。也就是说每个子类对象都是⼀个父类对象。
• 组合是⼀种has-a的关系。假设B组合了A每个B对象中都有⼀个A对象。
• 继承允许你根据父类的实现来定义子类的实现。这种通过⽣成子类的复用通常被称为白箱复用
(white-box reuse)。术语“白箱”是相对可视性而言在继承方式中父类的内部细节对子类可见
。继承⼀定程度破坏了父类的封装父类的改变对子类有很大的影响。子类和父类间的依赖关系
很强耦合度高。
• 对象组合是类继承之外的另⼀种复用选择。新的更复杂的功能可以通过组装或组合对象来获得。对
象组合要求被组合的对象具有良好定义的接⼝。这种复用风格被称为黑箱复用(black-box reuse)
因为对象的内部细节是不可见的。对象只以“黑箱”的形式出现。 组合类之间没有很强的依赖关
系耦合度低。优先使用对象组合有助于你保持每个类被封装。
• 优先使用组合而不是继承。实际尽量多去用组合组合的耦合度低代码维护性好。不过也不太
那么绝对类之间的关系就适合继承(is-a)那就用继承另外要实现多态也必须要继承。类之间的
关系既适合用继承(is-a)也适合组合(has-a)就用组合。
• 很多⼈说C语法复杂其实多继承就是⼀个体现。有了多继承就存在菱形继承有了菱形继承
就有菱形虚拟继承底层实现就很复杂性能也会有⼀些损失所以最好不要设计出菱形继承。多
继承可以认为是C的缺陷之⼀后来的⼀些编程语⾔都没有多继承如Java。