本章内容包括:

  • has-a关系
  • 包含对象成员的类
  • 模板类和valarray
  • 私有和保护继承
  • 多重继承
  • 虚基类
  • 创建类模板
  • 使用类模板
  • 模板具体化

14.1 包含对象成员的类

14.1.1 valarray类简介(数值数组模板类)

valarray<int> q_values;             //int数组

valarray<double> weights;        //double数组

回顾:

vector<double> vd(n);

array<int, 5> ai;

使用构造函数的例子:

double gpa[5] = { 3.1, 3.5, 3.8, 2.9, 3.3 };

valarray<double> v1;                        //长度为0的空数组

valarray<int> v2(8);                          //指定长度的空数组

valarray<int> v3(10, 8);                    //所有元素(8)被初始化为指定值(10)的数组

valarray<double> v4(gpa, 4);           //用常规数组(gpa)的值(前4个)进行初始化

valarray<int> v5 = { 20, 32, 17, 9 };  //初始化列表

valarray类方法:

  • operator[]()
  • size()
  • sum()
  • max()
  • min()

14.1.2 Student类设计

is-a模型:水果->香蕉

has-a模型:午餐->香蕉

student类的成员函数可以通过name和score使用string类和valarray类的公有方法,来操作string类的对象name和valarray类的对象score,但并不是基类和派生的继承关系。

一个类对象通过另一个类对象调用另一个类的公有方法

14.1.3 Student类示例

//程序清单 14.1 studentc.h
#pragma once
#ifndef STUDENTC_H_
#define STUDENTC_H_#include <iostream>
#include <string>
#include <valarray>using namespace std;class Student
{
private://typedef valarray<double> ArrayDb;using ArrayDb = valarray<double>;    //放私有意味着只可以在student类中使用ArrDbstring name;ArrayDb scores;ostream& arr_out(ostream& os) const;public:Student() : name("Null Student"), scores() {}//                                长度为0的double空数组//转换函数explicit Student(const string& s) : name(s), scores() {}//string类型显式转换为student类类型,禁止隐式转换explicit Student(int n) : name("Nully"), scores(n) {}//int类型显式转换为student类类型,包含长度为n的double空数组Student(const string& s, int n) : name(s), scores(n) {}//string和int类型的转换为student类类型Student(const string& s, const ArrayDb& a) : name(s), scores(a) {}Student(const string& s, const double* pd, int n) : name(s), scores(pd, n) {}~Student(){}double Average() const;const string& Name() const { return name; }                    //显示名称double& operator[](int i) { return scores[i]; }               //修改某一门成绩double operator[](int i) const { return scores[i]; }       //只读赋值friend istream& operator>>(istream& is, Student& stu);friend istream& getline(istream& is, Student& stu);friend ostream& operator<<(ostream& os, const Student& stu);};#endif // !STUDENTC_H_

对于继承的对象,构造函数在成员初始化列表中使用类名来调用特定的基类构造函数:

hasDMA::hasDMA(const hasDMA & hs) : baseDMA(hs) {...}

对于成员对象,构造函数则使用成员名:

Student(const char * str, const double * pd, int n) : name(str), score(pd, n) {}

//程序清单 14.2
#include "studentc.h"double Student::Average() const
{if (scores.size() > 0)    //元素个数return scores.sum() / scores.size();elsereturn 0;
}istream& operator>>(istream& is, Student& stu)
{is >> stu.name;return is;
}
istream& getline(istream& is, Student& stu)
{getline(is, stu.name);return is;
}
ostream& operator<<(ostream& os, const Student& stu)
{os << "Scores for " << stu.name << ":\n";stu.arr_out(os);return os;
}ostream& Student::arr_out(ostream& os) const
{int i;int lim = scores.size();if (lim > 0){for (i = 0; i < lim; i++){os << scores[i] << " ";if (i % 5 == 4)      //每行5个os << endl;}if (i % 5 != 0)os << endl;           //显示光标换行}elseos << "Empty array!";return os;
}
//程序清单 14.3
#include <iostream>
#include "studentc.h"const int pupils = 3;   //3个学士
const int quizzes = 5; //5门成绩
void set(Student& sa, int n);int main(void)
{Student ada[pupils] = { Student(quizzes), Student(quizzes), Student(quizzes) };int i;for (i = 0; i < pupils; i++)set(ada[i], quizzes);cout << "\nStudent List:\n";for (i = 0; i < pupils; i++)cout << ada[i].Name() << endl;cout << "\nResult:\n";for ( i = 0; i < pupils; i++){cout << ada[i];cout << "average: " << ada[i].Average() << endl << endl;}return 0;
}void set(Student& sa, int n)
{cout << "Enter the student's name: ";//调用friend istream& getline(istream& is, Student& stu)getline(cin, sa);cout << "Enter " << n << " quiz scores:\n";for (int i = 0; i < n; i++)//调用double& operator[](int n);cin >> sa[i];while (cin.get() != '\n')continue;
}

14.2 私有继承

  • 包含将对象作为一个命名的成员对象添加到类中
  • 私有继承将对象作为一个未被命名的继承对象添加到类中

14.2.1 Student类新版本

  • 包含版本提供两个被显式命名的对象成员
  • 私有继承提供两个无名称的隐式对象成员
//程序清单 14.4
#pragma once
#ifndef STUDENTI_H_
#define STUDENTI_H_#include <iostream>
#include <string>
#include <valarray>using namespace std;class Student : private string, private valarray<double>
//私有继承使用关键字private,多个基类的继承称为多重继承
{
private:typedef valarray<double> ArrayDb;//using ArrayDb = valarray<double>;ostream& arr_out(ostream& os) const;public://基类类名代替成员名Student() : string("Null Student"), ArrayDb() {}//转换函数explicit Student(const string& s) : string(s), ArrayDb() {}//string类型显式转换为student类类型,禁止隐式转换explicit Student(int n) : string("Nully"), ArrayDb(n) {}//int类型显式转换为student类类型,包含长度为n的double空数组Student(const string& s, int n) : string(s), ArrayDb(n) {}//string和int类型的转换为student类类型Student(const string& s, const ArrayDb& a) : string(s), ArrayDb(a) {}Student(const string& s, const double* pd, int n) : string(s), ArrayDb(pd, n) {}~Student() {}double Average() const;const string& Name() const { return (const string&) *this; }                    //显示名称double& operator[](int i) { return ArrayDb::operator[](i); }              //修改某一门成绩double operator[](int i) const { return ArrayDb::operator[](i); }      //只读赋值friend istream& operator>>(istream& is, Student& stu);friend istream& getline(istream& is, Student& stu);friend ostream& operator<<(ostream& os, const Student& stu);using valarray<double>::max;
};#endif // !STUDENTI_H_

1.类名代替成员名

//包含
Student(const string& s, const double* pd, int n) : name(s), scores(pd, n) {}
//私有
Student(const string& s, const double* pd, int n) : string(s), ArrayDb(pd, n) {}

2.访问基类方法:类名+::

//包含
double Student::Average() const
{if (scores.size() > 0)    //元素个数return scores.sum() / scores.size();elsereturn 0;
}
//私有
double Student::Average() const
{if (ArrDb::size() > 0)    //元素个数return ArrDb::sum() / scores.size();elsereturn 0;
}

3.访问基类对象:强制类型转换

//包含
const string& Name() const { return name; }
//私有
const string& Name() const { return (const string &) *this; }

4.访问基类友元:强制类型转换

//包含
ostream& operator<<(ostream& os, const Student& stu)
{os << "Scores for " << stu.name << ":\n";stu.arr_out(os);return os;
}
//私有
ostream& operator<<(ostream& os, const Student& stu)
{os << "Scores for " << (const string &) stu << ":\n";stu.arr_out(os);return os;
}

派生类成员函数和友元通过通过强制类型转换访问基类成员,仅限某类型单个对象,多个不好使

//程序清单 14.5
#include "studenti.h"double Student::Average() const
{if (ArrayDb::size() > 0)return ArrayDb::sum() / ArrayDb::size();elsereturn 0;
}istream& operator>>(istream& is, Student& stu)
{is >> (string&) stu;return is;
}
istream& getline(istream& is, Student& stu)
{getline(is, (string&) stu);return is;
}
ostream& operator<<(ostream& os, const Student& stu)
{os << "Scores for " << (const string&) stu << ":\n";stu.arr_out(os);return os;
}ostream& Student::arr_out(ostream& os) const
{int i;int lim = ArrayDb::size();if (lim > 0){for (i = 0; i < lim; i++){os << ArrayDb::operator[](i) << " ";if (i % 5 == 4)       //每行5个os << endl;}if (i % 5 != 0)os << endl;           //显示光标换行}elseos << "Empty array!";return os;
}
//程序清单 14.6
#include <iostream>
#include "studenti.h"const int pupils = 3;   //3个学士
const int quizzes = 5; //5门成绩
void set(Student& sa, int n);int main(void)
{Student ada[pupils] = { Student(quizzes), Student(quizzes), Student(quizzes) };int i;for (i = 0; i < pupils; i++)set(ada[i], quizzes);cout << "\nStudent List:\n";for (i = 0; i < pupils; i++)cout << ada[i].Name() << endl;cout << "\nResult:\n";for (i = 0; i < pupils; i++){cout << ada[i];cout << "average: " << ada[i].Average() << endl;//要让基类方法在派生类外可用,使用using声明,函数下放cout << "max: " << ada[i].max() << endl << endl;}return 0;
}void set(Student& sa, int n)
{cout << "Enter the student's name: ";//调用friend istream& getline(istream& is, Student& stu)getline(cin, sa);cout << "Enter " << n << " quiz scores:\n";for (int i = 0; i < n; i++)cin >> sa[i];while (cin.get() != '\n')continue;
}

14.2.2 使用包含还是私有继承

  • 多数C++倾向包含,首先易于理解
  • 如果某个类需要3个string对象,可以使用包含声明3个独立的string成员,但私有继承只能使用一个
  • 派生类可以重新定义虚函数,包含类不能
  • 通常,应使用包含建立has-a关系;如果新类需要访问原有类的保护成员,或需要重新定义虚函数,则使用私有继承。

14.2.3 保护继承

  • 保护继承是私有继承的变体,使用关键字protect
  • 使用保护继承,基类的公有成员和保护成员都将成员派生类的保护成员
  • 当从派生类再派生出另一个类时,私有继承和保护继承的区别便体现出来:
  • 使用私有继承时,第三代类不能使用基类接口,因为基类方法在派生类中变成私有方法;使用保护继承时,基类公有方法在第一代中变成保护,第三代派生可以使用。

14.2.4 使用using重新定义访问权限

使用保护派生或私有派生时,基类的公有成员将成为派生类的保护成员或私有成员

要让基类方法在派生类外可用:

方法一:定义一个使用该基类方法的派生类方法

double Student::sum() const
{return std::valarry<double>::sum();
}

方法二:使用using声明,函数下放。using声明只使用成员名——没有圆括号、函数特征标和返回类型

class Student : private std::string, private std::vaalarray<double>
{
...
public:using std::valarray<double>::min;using std::valarray<double>::max;
...
}
//就像Student的公有方法:
cout << "high score: " << ada[i].max() << endl;

14.3 多重继承

两个主要问题:

  • 从两个不同的基类继承同名方法;
  • 从两个或多个相关基类继承同一个类的多个实例

//程序清单 14.7 Worker0.h
#pragma once
#ifndef WORKER0_H_
#define WORKER0_H_#include <iostream>
#include <string>using namespace std;class Worker
{
public:Worker() : fullname("no one"), id(0L) {}Worker(const string& s, long n) : fullname(s), id(n) {}virtual ~Worker() = 0;     //纯虚函数,要在cpp中定义virtual void Set();virtual void Show() const;private:string fullname;long id;};class Waiter : public Worker   //不写public默认私有继承
{
public:Waiter() : Worker(), panache(0) {}Waiter(const string& s, long n, int p) : Worker(s, n), panache(p) {}Waiter(const Worker& wk, int p) : Worker(wk), panache(p) {}virtual ~Waiter() {}virtual void Set();virtual void Show() const;private:int panache;};class Singer : public Worker
{
protected:enum { other, alto, contralto, soprano, bass, baritone, tenor };const static int Vtypes = 7;private:static char* pv[Vtypes];     //静态字符串数组指针,声明中声明,方法中初始化//不属于类,类似全局,所有对象共享一个int voice;public:Singer() : Worker(), voice(other) {}Singer(const string& s, long n, int v = other) : Worker(s, n), voice(v) {}Singer(const Worker& wk, int v = other) : Worker(wk), voice(v) {}virtual ~Singer() {}virtual void Set();virtual void Show() const;};#endif // !WORKER0_H_
//程序清单 14.8
#include "worker0.h"Worker::~Worker() {}void Worker::Set()
{cout << "Enter worker's name: ";getline(cin, fullname);cout << "Enter worker's ID: ";cin >> id;while (cin.get() != '\n')        //数字字符串混合输入,消耗换行continue;
}
void Worker::Show() const
{cout << "Name: " << fullname << endl;cout << "Employee ID: " << id << endl;
}void Waiter::Set()
{Worker::Set();     //类名+::派生类调用基类方法cout << "Enter waiter's panache rating: ";cin >> panache;while (cin.get() != '\n')continue;
}
void Waiter::Show() const
{cout << "Category: waiter\n";Worker::Show();cout << "Panache rating: " << panache << endl;
}//初始化时,使用::指出静态成员所属的类
char* Singer::pv[] = { (char*)"other", (char*)"alto", (char*)"contralto",(char*)"soprano", (char*)"bass", (char*)"baritone", (char*)"tenor" };
//char* Singer::pv[] = { "other", "alto", "contralto",
//              "soprano", "bass", "baritone", "tenor" };
//项目>>属性>>C/C++>>语言>>符合模式>>改成否void Singer::Set()
{Worker::Set();cout << "Enter number for singer's vocal range:\n";int i;for ( i = 0; i < Vtypes; i++){cout << i << ": " << pv[i] << " ";if (3 == i % 4)        //一行4个cout << endl;}if (0 != i % 4)      //最后光标换行cout << endl;
//程序清单 14.9
#include <iostream>
#include "worker0.h"const int LIM = 4;int main(void)
{Waiter bob("Bob Apple", 314L, 5);Singer bev("Beverly Hills", 522L, 3);Waiter w_temp;Singer s_temp;Worker* pw[LIM] = { &bob, &bev, &w_temp, &s_temp };//基类指针(引用)可以指向基类对象/派生类对象int i;for ( i = 2; i < LIM; i++)pw[i]->Set();    //看指针指向类型决定调用哪个set()for ( i = 0; i < LIM; i++){pw[i]->Show();cout << endl;}return 0;
}

14.3.1 有多少worker

从Singer和Waiter公有派生出SingingWaiter:

class SingingWaiter : public Singer, public Waiter { ... }

SingingWaiter包含两个Worker组件

SingingWaiter ed;

Worker * pw = &ed;        //二义性

要改为:

Worker * pw1 = (Waiter *) &ed;

Worker * pw2 = (Singer *) &ed;

应该只包含一个基类组件——虚基类

1. 虚基类(与虚函数无关)

虚基类使多个类(基类相同)派生的对象只继承一个基类对象

class Singer : virtual public Worker { ... };

class Waiter : public virtual Worker { ... };

2. 新的构造函数规则:

传统:层层传递

14.3.2 哪个方法:模块化

//程序清单 14.10
#pragma once
#ifndef WORKERMI_H_
#define WORKERMI_H_#include <iostream>
#include <string>using namespace std;class Worker
{
public:Worker() : fullname("no one"), id(0L) {}Worker(const string& s, long n) : fullname(s), id(n) {}virtual ~Worker() = 0;     //纯虚函数,要在cpp中定义virtual void Set() = 0;virtual void Show() const = 0;private:string fullname;long id;protected:virtual void Data() const;virtual void Get();
};class Waiter : virtual public Worker  //不写public默认私有继承
{
public:Waiter() : Worker(), panache(0) {}Waiter(const string& s, long n, int p) : Worker(s, n), panache(p) {}Waiter(const Worker& wk, int p) : Worker(wk), panache(p) {}virtual ~Waiter() {}virtual void Set();virtual void Show() const;private:int panache;protected:virtual void Data() const;virtual void Get();};class Singer : public virtual Worker
{
protected:enum { other, alto, contralto, soprano, bass, baritone, tenor };const static int Vtypes = 7;virtual void Data() const;virtual void Get();private:static char* pv[Vtypes];        //静态字符串数组指针,声明中声明,方法中初始化int voice;public:Singer() : Worker(), voice(other) {}Singer(const string& s, long n, int v = other) : Worker(s, n), voice(v) {}Singer(const Worker& wk, int v = other) : Worker(wk), voice(v) {}virtual ~Singer() {}virtual void Set();virtual void Show() const;};class SingingWaiter : public Waiter, public Singer
{
public:SingingWaiter() {}SingingWaiter(const string& s, long n, int p = 0, int v = other): Worker(s, n), Waiter(s, n, p), Singer(s, n, v) {}SingingWaiter(const Worker& wk, int p = 0, int v = other): Worker(wk), Waiter(wk, p), Singer(wk, v) {}SingingWaiter(const Waiter& wt, int v = other): Worker(wt), Waiter(wt), Singer(wt, v) {}SingingWaiter(const Singer& sg, int p = 0):Worker(sg), Waiter(sg, p), Singer(sg) {}~SingingWaiter() {}void Set();void Show() const;
protected:virtual void Data() const;virtual void Get();};#endif // !WORKER0_H_
//程序清单 14.11
#include "workermi.h"Worker::~Worker() {}void Worker::Get()
{getline(cin, fullname);cout << "Enter worker's ID: ";cin >> id;while (cin.get() != '\n')     //数字字符串混合输入,消耗换行continue;
}
void Worker::Data() const
{cout << "Name: " << fullname << endl;cout << "Employee ID: " << id << endl;
}void Waiter::Get()
{cout << "Enter waiter's panache rating: ";cin >> panache;while (cin.get() != '\n')continue;
}
void Waiter::Data() const
{cout << "Panache rating: " << panache << endl;
}
void Waiter::Set()
{cout << "Enter waiter's name: ";Worker::Get();Get();
}
void Waiter::Show() const
{cout << "Category: waiter\n";Worker::Data();Data();
}//初始化时,使用::指出静态成员所属的类
char* Singer::pv[] = { (char*)"other", (char*)"alto", (char*)"contralto",(char*)"soprano", (char*)"bass", (char*)"baritone", (char*)"tenor" };
//char* Singer::pv[] = { "other", "alto", "contralto",
//              "soprano", "bass", "baritone", "tenor" };
//项目>>属性>>C/C++>>语言>>符合模式>>改成否void Singer::Get()
{cout << "Enter number for singer's vocal range:\n";int i;for (i = 0; i < Vtypes; i++){cout << i << ": " << pv[i] << " ";if (3 == i % 4)       //一行4个cout << endl;}if (0 != i % 4)      //最后光标换行cout << endl;while (cin >> voice && (voice < 0 || voice >= Vtypes))cout << "Please enter a value >= 0 and <= " << Vtypes << endl;while (cin.get() != '\n')continue;
}
void Singer::Data() const
{cout << "Vocal range: " << pv[voice] << endl;
}
void Singer::Set()
{cout << "Enter singer's name: ";Worker::Get();Get();
}
void Singer::Show() const
{cout << "Category: singer\n";Worker::Data();Data();
}void SingingWaiter::Data() const
{Waiter::Data();Singer::Data();
}
void SingingWaiter::Get()
{Waiter::Get();Singer::Get();
}
void SingingWaiter::Set()
{cout << "Enter singingwaiter's name: ";Worker::Get();Get();
}
void SingingWaiter::Show() const
{cout << "Category: singing waiter\n";Worker::Data();Data();
}
//程序清单 14.12
#include <iostream>
#include "workermi.h"
#include <cstring>const int SIZE = 3;int main(void)
{Worker* lolas[SIZE];int ct;for ( ct = 0; ct < SIZE; ct++){char choice;cout << "Enter the employee categlory:\n"<< "w: waiter  s: singer  t: singing waiter  q: quit\n";cin >> choice;while (strchr("wstq", choice) == NULL){cout << "Enter w, s, t or q: ";cin >> choice;}if (choice == 'q' || choice == 'Q')break;switch (choice){case 'w':lolas[ct] = new Waiter; break;case 's':lolas[ct] = new Singer; break;case 't':lolas[ct] = new SingingWaiter; break;default:break;}cin.get();lolas[ct]->Set();}cout << "\nHere is your staff:\n";int i;for ( i = 0; i < ct; i++){cout << endl;lolas[i]->Show();}for ( i = 0; i < ct; i++)delete lolas[i];cout << "Bye.\n";return 0;
}

14.4 类模板

模板提供参数化类型,即能将类型名作为参数传递给接收方来建立类或函数

14.4.1 定义类模板

//程序清单 14.13
#pragma once
#ifndef STACKTP_H_
#define STACKTP_H_#include <iostream>using namespace std;//模板类
template <class Type>
//template <typename Type>
class Stack
{
public:Stack();bool isempty(void) const;bool isfull(void) const;bool push(const Type& item);bool pop(Type& item);private:enum { MAX = 10 };Type items[MAX];int top;};//每个函数头都以相同的模板声明打头
template <class Type>
//将类限定符改为Stack<Type>::
Stack<Type>::Stack(void)
{top = 0;
}template <class Type>
bool Stack<Type>::isempty(void) const
{return top == 0;
}template <class Type>
bool Stack<Type>::isfull(void) const
{return top == MAX;
}template <class Type>
bool Stack<Type>::push(const Type& item)
{if (top < MAX){items[top++] = item;return true;}elsereturn false;
}template <class Type>
bool Stack<Type>::pop(Type& item)
{if (top > 0){item = items[--top];return true;}elsereturn false;
}
#endif // !STACKTP_H_
  • 模板不是类和成员函数定义,只是编译器指令
  • 模板的具体实现,称为实例化和具体化
  • 不能将模板成员函数放在独立的实现文件中
  • 将所有模板信息放在一个头文件中

14.4.2 使用模板类

//程序清单 14.14
#include <iostream>
#include <string>
#include <cctype>
#include "stacktp.h"int main(void)
{Stack<string> st;char ch;string po;cout << "Enter A to add a purchase, P to process a PO, or Q to quit.\n";while (cin >> ch && toupper(ch) != 'Q'){while (cin.get() != '\n')continue;if (!isalpha(ch)){cout << '\a';continue;}switch (ch){case 'A':case 'a':cout << "Enter a PO number add: ";cin >> po;if (st.isfull())cout << "stack already full.\n";elsest.push(po);break;case 'P':case 'p':if (st.isempty())cout << "stack already empty.\n";else{st.pop(po);cout << "PO #" << po << " popped.\n";}break;default:break;}cout << "Enter A to add a purchase, P to process a PO, or Q to quit.\n";}cout << "Bye.\n";return 0;
}
  • 生成模板类必须实例化
  • 使用具体类型替换泛型名

下面代码创建两个栈:一个存储int,一个存储string对象

Stack<int> kernels;

Stack<string> colonels;

泛型标识符——Type——称为类型参数,类似变量,赋给的不能是数字,只能是类型

14.4.3 深入探讨模板类

可以用char指针替换string对象吗?可以,需要对程序做重大修改

1.不正确使用指针栈:

Stack<char *> st;

版本1:

string po 替换为:char * po;

仅仅创建指针,没有创建保存字符串的空间

版本2:

string po 替换为:char po[40];

数组完全与pop()方法冲突

版本3:

string po 替换为:char * po = new char [40];

栈没有保存每一个新字符

2.正确使用指针栈:

让调用程序提供一个指针数组,其中每个指针都指向不同的字符串

//程序清单 14.15
#pragma once
#ifndef STCKTP1_H_
#define STCKTP1_H_#include <iostream>using namespace std;template <class Type>
class Stack
{
public:explicit Stack(int ss = SIZE);      //显式转换函数Stack(const Stack& st);             //复制构造函数~Stack() { delete[] items; }bool isempty() { return top == 0; }bool isfull() { return top == stacksize; }bool push(const Type& item);bool pop(Type& item);Stack& operator=(const Stack& st);private:enum { SIZE = 10 };int stacksize;Type* items;int top;};template <class Type>
Stack<Type>::Stack(int ss) : stacksize(ss), top(0)
{items = new Type[stacksize];
}
template <class Type>
Stack<Type>::Stack(const Stack& st)
{stacksize = st.stacksize;top = st.top;items = new Type[stacksize];for (int i = 0; i < top; i++)items[i] = st.items[i];
}
template <class Type>
bool Stack<Type>::push(const Type& item)
{if (top < stacksize){items[top++] = item;return true;}elsereturn false;
}
template <class Type>
bool Stack<Type>::pop(Type& item)
{if (top > 0){item = items[--top];return true;}elsereturn false;
}
template <class Type>
Stack<Type>& Stack<Type>::operator=(const Stack<Type>& st)
{if (this == &st)return *this;delete[] items;stacksize = st.stacksize;top = st.top;items = new Type[stacksize];for (int i = 0; i < top; i++)items[i] = st.items[i];return *this;
}#endif // !STCKTP1_H_
//程序清单 14.16
#include <iostream>
#include "stcktp1.h"
#include <cstdlib>
#include <ctime>const int Num = 10;int main(void)
{srand(time(0));cout << "Enter stack size: ";int stacksize;cin >> stacksize;Stack<const char*> st(stacksize);const char* in[Num] ={"1: Hank", "2: Kiki", "3: Betty", "4: Ian", "5: Wolfgang","6: Portia", "7: Joy", "8: Xaverie", "9: Juan", "10: Miasha"};const char* out[Num];int processed = 0;int nextin = 0;while (processed < Num){if (st.isempty())st.push(in[nextin++]);else if (st.isfull())st.pop(out[processed++]);else if (rand() % 2 && nextin < Num)st.push(in[nextin++]);elsest.pop(out[processed++]);}for (int i = 0; i < Num; i++)cout << out[i] << endl;cout << "Bye.\n";return 0;
}

14.4.4 数组模板示例和非类型参数

//程序清单 14.17
#pragma once
#ifndef ARRAYTP_H_
#define ARRAYTP_H_#include <iostream>
#include <cstdlib>using namespace std;template <class T, int n>
class ArrayTP
{
public:ArrayTP() {}explicit ArrayTP(const T& v);virtual T& operator[](int i);virtual T operator[] (int i) const;private:T ar[n];
};template <class T, int n>
ArrayTP<T, n>::ArrayTP(const T& v)
{for (int i = 0; i < n; i++)ar[i] = v;
}template <class T, int n>
T& ArrayTP<T, n>::operator[](int i)
{if (i < 0 || i >= n){cerr << "Error in array limits: " << i << " is out of range.\n";exit(EXIT_FAILURE);}return ar[i];
}template <class T, int n>
T ArrayTP<T, n>::operator[] (int i) const
{if (i < 0 || i >= n){cerr << "Error in array limits: " << i << " is out of range.\n";exit(EXIT_FAILURE);}return ar[i];
}#endif // !ARRAYTP_H_

template <class T, int n>

T为类型参数,int 为非类型或表达式参数

表达式参数可以是整型、枚举、引用和指针(例如double m 不合法,double &rm和double *pm合法)

模板代码不能修改参数的值,表达式的值必须是常量表达式

不同类不同对象

ArrayTP<double, 12> eggweight;

ArrayTP<double, 13> donuts;

同一类不同对象

Stack<int> egg(12);

Stack<int> dunkers(13);

14.4.5 模板多功能性

1.递归使用模板

ArrayTP<ArrayTP<int, 5>, 10> twodee;

等价于:

int twodee[10][5];

//程序清单 14.18
#include <iostream>
#include "arraytp.h"int main(void)
{ArrayTP<int, 10> sums;ArrayTP<double, 10> aves;ArrayTP<ArrayTP<int, 5>, 10> twodee;int i, j;for ( i = 0; i < 10; i++){sums[i] = 0;for ( j = 0; j < 5; j++){twodee[i][j] = (i + 1) * (j + 1);sums[i] += twodee[i][j];}aves[i] = (double)sums[i] / 10;}for ( i = 0; i < 10; i++){for ( j = 0; j < 5; j++){cout.width(2);cout << twodee[i][j] << ' ';}cout << ": sum = ";cout.width(3);cout << sums[i] << ", average = " << aves[i] << endl;}cout << "Done.\n";return 0;
}

2.使用多个类型参数

//程序清单 14.19
#include <iostream>
#include <string>using namespace std;template <class T1, class T2>
class Pair
{
public:Pair() {}Pair(const T1& aval, const T2& bval) : a(aval), b(bval) {}T1& first();T2& second();T1 first() const { return a; }T2 second() const { return b; }private:T1 a;T2 b;
};template<class T1, class T2>
T1& Pair<T1, T2>::first()
{return a;
}template<class T1, class T2>
T2& Pair<T1, T2>::second()
{return b;
}int main(void)
{Pair<string, int> ratings[4] ={Pair<string, int>("AAA", 4),Pair<string, int>("BBB", 3),Pair<string, int>("CCC", 2),Pair<string, int>("DDD", 1)};int joints = sizeof(ratings) / sizeof(Pair<string, int>);cout << "Rating:\t Eatery\n";for (int i = 0; i < joints; i++)cout << ratings[i].second() << ":\t" << ratings[i].first() << endl;cout << "Oops! Revised rating:\n";ratings[3].first() = "EEE";ratings[3].second() = 5;cout << ratings[3].second() << ":\t" << ratings[3].first() << endl;return 0;
}

3.默认类型模板参数

类模板的另一项新特性是可以为类型参数提供默认值:

template <class T1, class T2 = int>

class Topo { ... }

Topo<double, double> m1;        //T1 is double, T2 is double

Topo<doubel> m2;                     //T1 is double, T2 is int

可以为类模板参数提供默认值,但不能为函数模板提供默认值

可以为非类型参数提供默认值,类模板和函数模板都适用

14.4.6 模板具体化

1.隐式实例化(用时再准备)

ArrayTP<int, 100> stuff;

编译器在需要对象前,不会生成类的隐式实例化

ArrayTP<double, 30> * pt;

pt = new ArrayTP<double, 30>;

2.显式实例化(不用也准备)

使用关键字template并指出所需类型来声明类时,编译器将生成类声明的显式实例化

template class ArrayTP<string, 100>;

虽然没有创建或提及类对象,编译器也将生成类声明(包括方法定义)

3.显式具体化(特殊类型特殊处理、特事特办)

4.部分具体化:

通用模板:template <class T1, class T2> class Pair { ... }

部分具体化模板:template <class T1> class Pair<T1, int> { ... },template后面是没有被具体化的类型参数

14.4.7 成员模板

模板可用作结构、类或模板类的成员。

//程序清单 14.20
#include <iostream>using namespace std;//模板类beta
template <typename T>
class beta
{
public:beta(T t, int i) : q(t), n(i) {}//模板函数blabtemplate <typename U>U blab(U u, T t) { return (n.Value() + q.Value()) * u / t; }void Show() const { q.show(); n.show(); }private://模板类holdtemplate <typename V>class hold{public:hold(V v = 0) : val(v) {}void show() const { cout << val << endl; }V Value() const { return val; }private:V val;};hold <T> q;hold <int> n;};int main(void)
{beta<double> guy(3.5, 3);cout << "T was set to double\n";guy.Show();cout << "V was set to T, which is double, then V was set to int\n";cout << guy.blab(10, 2.3) << endl;cout << "U was set to int.\n";cout << guy.blab(10.0, 2.3) << endl;cout << "U was set to double.\n";return 0;
}

14.4.8 将模板用作参数

//程序清单 14.21
#include <iostream>
#include "stacktp.h"using namespace std;template <template <typename T> class thing>
class Crab
{
public:Crab() {}bool push(int a, double x) { return s1.push(a) && s2.push(x); }bool pop(int& a, double& x) { return s1.pop(a) && s2.pop(x); }private:thing<int> s1;thing<double> s2;
};int main(void)
{Crab<Stack> nebula;int ni;double nb;cout << "Enter int double pairs, such as 4 3.5 (0 0 to end):\n";while (cin >> ni >> nb && ni > 0 && nb > 0){if (!nebula.push(ni, nb))break;}while (nebula.pop(ni, nb))cout << ni << ", " << nb << endl;cout << "Done\n";return 0;
}

14.4.9 模板类和友元

  • 非模板友元:在模板类中将一个常规函数声明为友元
//程序清单 14.22
//非模板友元
#include <iostream>using namespace std;template<typename T>
class HasFriend
{
public:HasFriend(const T& i) : item(i) { ct++; }~HasFriend() { ct--; }friend void counts();//是所以HasFriend模板类的友元函数,HasFriend<int>和HasFriend<double>共用counts()friend void reports(HasFriend<T>& ht);private:T item;static int ct;//相同类共用ct,类方法中初始化,HasFriend<int>和HasFriend<double>有不同ct
};template <typename T>
int HasFriend<T>::ct = 0;void counts()
{cout << "int count: " << HasFriend<int>::ct << "; ";cout << "double count: " << HasFriend<double>::ct << endl;
}//非模板友元重载
void reports(HasFriend<int>& hf)
{cout << "HasFriend<int>: " << hf.item << endl;
}
void reports(HasFriend<double>& hf)
{cout << "HasFriend<double>: " << hf.item << endl;
}int main(void)
{cout << "No objects declared: ";counts();HasFriend<int> hfi1(10);cout << "After hfi1 declared: ";counts();HasFriend<int> hfi2(20);cout << "After hfi2 declared: ";counts();HasFriend<double> hfdb(10.5);cout << "After hfdb declared: ";counts();reports(hfi1);reports(hfi2);reports(hfdb);return 0;
}
  • 约束模板友元:
//程序清单 14.23
//约束模板友元
#include <iostream>using namespace std;template<typename T> void counts();
template<typename T> void reports(T&);template<typename TT>
class HasFriend
{
public:HasFriend(const TT& i) : item(i) { ct++; }~HasFriend() { ct--; }friend void counts<TT>();friend void reports<>(HasFriend<TT>& ht);
//或friend void reports<HasFriend>(HasFriend<TT>& ht);private:TT item;static int ct;//相同类共用ct,类方法中初始化,HasFriend<int>和HasFriend<double>有不同ct
};template <typename T>
int HasFriend<T>::ct = 0;//HasFriend<int>和HasFriend<double>有不同counts()
template <typename T>
void counts()
{cout << "template size: " << sizeof(HasFriend<T>) << "; ";cout << "template counts: " << HasFriend<T>::ct << endl;
}
template <typename T>
void reports(T& hf)
{cout << hf.item << endl;
}int main(void)
{counts<int>();HasFriend<int> hfi1(10);HasFriend<int> hfi2(20);HasFriend<double> hfdb(10.5);reports(hfi1);reports(hfi2);reports(hfdb);cout << "counts<int>() output:\n";//模板函数具体化counts<int>();cout << "counts<double>() output:\n";counts<double>();return 0;
}
  • 非约束模板友元:
//程序清单 14.24
#include <iostream>using namespace std;template <typename T>
class ManyFriend
{
public:ManyFriend(const T& i) : item(i) {}template <typename C, typename D> friend void show2(C&, D&);private:T item;
};template <typename C, typename D> void show2(C& c, D& d)
{cout << c.item << ", " << d.item << endl;
}int main(void)
{ManyFriend<int> hfi1(10);ManyFriend<int> hfi2(20);ManyFriend<double> hfdb(10.5);cout << "hfi1, hfi2: ";show2(hfi1, hfi2);cout << "hfi2, hfdb: ";show2(hfi2, hfdb);return 0;
}

14.4.10 模板别名

C++_primer_plus学习笔记 第14章 C++中的代码重用相关推荐

  1. 《C Primer Plus》学习笔记—第14章

    目录 <C Primer Plus>学习笔记 第14章 结构和其他数据形式 1.示例问题:创建图书目录 1.程序book.c 2.建立结构声明 3.定义结构变量 1.初始化结构 2.访问结 ...

  2. 《统计学》(贾俊平)考研初试完整学习笔记10~14章

    贾俊平<统计学第6版>学习笔记 这是我自己去年考研时整理的笔记,希望能给到432考研以及正在学习统计学的小伙伴们一点帮助吧,我是把这份笔记当作复习时的框架来用的,时不时过一遍,有不熟悉的地 ...

  3. (Java零基础学习笔记)第二章 Java中的基本语法

    前言: 大家好! 我是BA unravel .如果你想和我一起学习JAVA,欢迎大家一起来学习这个世界上最好的语言! 学习目标: 一周掌握 Java 入门知识 学习内容: 1. 搭建 Java 开发环 ...

  4. 2022 最新 Android 基础教程,从开发入门到项目实战【b站动脑学院】学习笔记——第五章:中级控件

    第 5 章 中级控件 本章介绍App开发常见的几类中级控件的用法,主要包括:如何定制几种简单的图形.如何使用几种选择按钮.如何高效地输入文本.如何利用对话框获取交互信息等,然后结合本章所学的知识,演示 ...

  5. 《UVM实战卷Ⅰ》学习笔记 第七章 UVM中的寄存器模型(2)

    推荐另外一篇相对更为基础的UVM 寄存器相关知识梳理UVM寄存器模型笔记_IC-V的博客-CSDN博客 目录 7.3 后门访问和前门访问 1.后门访问的方式 7.4复杂的寄存器模型 7.5复杂寄存器模 ...

  6. 计算机图形学与opengl C++版 学习笔记 第14章 其他技术

    目录 14.1 雾 14.2 复合.混合.透明度 14.3 用户定义剪裁平面 14.4 3D纹理 14.5 噪声 14.6 噪声应用--大理石 14.8 噪声应用--云 14.9 噪声应用--特殊效果 ...

  7. 吴恩达机器学习学习笔记第六章:机器学习中的线性代数操作python3版(含numpy、panda库的使用)

    pracitice1:是针对矩阵元素的基本运算 #practice1是针对矩阵元素的基本运算 import numpy as np A=np.array([[1,2,3],[3,4,5]])#列表转换 ...

  8. 《UVM实战》学习笔记——第四章 UVM中的TLM1.0通信

    文章目录 前言 一.TLM1.0 1.TLM的定义 2.数据流:数据流动的方向 3.控制流:动作发起者initiator.动作接收者target 4.各种端口的连接 5.transport 6.non ...

  9. 《C++ Primer Plus(第六版)》(30)(第十四章 C++中的代码重用 编程题答案)

    14.7 编程练习 1.Wine类有一个string类对象成员(参见第4章)和一个Pair对象(参见本章):其中前者用来存储葡萄酒的名称,而后者有2个valarry<int>对象(参见本章 ...

  10. 【C++ Primer】第十四章 C++中的代码重用

    序:C++的一个主要目标是促进代码重用,其中包含公有继承.包含.使用私有或保护继承 一,包含对象成员的类        1)valarray类简介  #include <valarray> ...

最新文章

  1. PHP $_REQUEST获取表单提交的代码
  2. Same Sum Blocks
  3. LeetCode 410——分割数组的最大值
  4. Aspose.Words简单生成word文档
  5. 如何给定两个gps坐标 算出航向角_机器人开发如何配置ROS中的TF变换关系?
  6. Zookeeper单机安装(开启kerberos)
  7. scikit-learn学习之贝叶斯分类算法
  8. Android 进阶第二篇——性能优化
  9. react-native学习小结
  10. 268、缺失数字(python)
  11. VMware vSphere 7.0U3下载
  12. QCC3040----SOC模块
  13. 疫情后,超七成居民理财偏好趋于保守
  14. excel表格快捷键
  15. 云剪智能混剪软件/批量剪辑工具技术源码框架---- 一键生成上亿条原创视频
  16. vue切换路由不重新渲染_Vue来回切换页面不重新加载 --keep-alive
  17. Hadamard矩阵和Kronecker积
  18. jq div拖动(移动端和pc端)
  19. Linux6.8搭建sftp服务
  20. 红米note4 android o,小米红米Note4/mido-LOS-安卓9.0.0-稳定版Stable2.0-来去电归属-农历等-本地化增强适配...

热门文章

  1. linux 笔记本 显卡驱动,archlinux 笔记本安装nvidia显卡驱动与intel显卡驱动
  2. 六级单词词汇表(有注音)
  3. 服务器被攻击了怎么办
  4. 常见的web网站攻击类型
  5. 倒杨辉三角4行c语言,倒杨辉三角
  6. HTML之meta属性大全
  7. Spring Boot+Spring Security:基于URL动态权限:自定义Filter - 第17篇
  8. Mangos地区代码
  9. MyEclipse配置jdk
  10. Android开发 ANR异常的解决(应用程序无响应)