类
private 私有成员
仍可被同类的其他对象函数直接调用
类中的内联函数
类声明中定义的函数等价于,在类声明中提供原型,然后在在类声明后提供内联函数定义,即,类声明中的成员函数定义是内联函数。
构造函数,无类型(void也没有)。若提供显式构造函数,则不要忘记写默认构造函数。
调用默认构造函数不要加括号,不然会误认为是函数定义。
Stock stocks();//Error!!! 这应该是返回值为Stock类的函数声明
const 成员函数
bool IsFull() const;
保证不在这个成员函数中修改该对象
char & operator[](int i);
const char operator[](int i) const;//返回const变量,不能用于左值
构造函数
无返回值(也不是void),函数名就是类名。
class Stack
{
private:
...
public:
...
Stack();
...
};
参数只有一个时,可以通过赋值的方式初始化给一个值(类型转换)
class time
{
private:
long int time;
int hours;
int min;
int seconds;
public:
time();
time(const long t);
...
};
...
time T=12;
//time T=time(12);
//time T(12);
当自己定义构造函数时,必须再定义默认构造函数,否则time T1;将错误
成员初始化列表
class time
{
private:
long int time;
int hours;
int min;
int seconds;
public:
time();
time(const long t);
time(const int h,const int m,const int s);
...
};
...
time(const int h,const int m,const int s):hours(h),min(m),seconds(s)
{}
类内初始化(C++11)
class Classy
{
int mem1=10;
const int cmen2=12;
...
}
析构函数
class Stack
{
private:
...
public:
...
~Stack();
...
};
类的类型转换和强制类型转换
类->其它类型 的自动转换
只有接受一个参数的构造函数就是(类->其它类型 的)转换函数。
假设类 fruit 存在转换函数—— fruit(double x);
则可以:
class fruit
{
...
public:
fruit(double x);
fruit(int n,double d=12.3);
...
}
...
fruit apple,pie;
apple = 12.4;//也叫 隐式转换,转换函数也叫自动类型转换函数
pie = 6;
等价于: apple - fruit(12.4);
即 用12.4作为参数构造出一个临时对象复制给apple
其它类型->类 的自动转换
转换函数:
必须是类方法
不能指定返回类型(但有返回值)
不能有参数
class fruit
{
private:
double weight;
…
public:
operator double(){return weight};
fruit(double w){weight = w;}
…
}
…
fruit apple(12.6);
double x = apple; //x=12.6
关闭自动类型转换——关键字 explicit
explicit fruit(int n,double d=12.3);
explicit operator double ();//c++11
但仍可强制类型转换
注意:
复制构造函数及其一些问题
赋值运算符(=)的一些问题
对象数组
对象数组必须有默认构造函数。
因为对象数组的初始化肯定经过默认初始化
类中的常量
一,const常量(只能用成员列表初始化)
首先,不能用 const 声明常量,因为类声明并不分配内存,而const常量的初始化必须在分配内存时进行。但是一旦对象进入构造函数,内存就已经分配,也就无法初始化,使用办法是在进入构造函数前就对其初始化——成员初始化列表。
class Stack
{
private:
const int MAX;
...
public:
Stack(int x);
};
...
Stack::Stack(int x):MAX(x)
{}
二,枚举
无名枚举
class Stack
{
private:
enum {MAX=10};
…
public:
…
};
作用域内枚举
两个枚举中的同名枚举量可能发生冲突,所以以下代码是错误的
enum egg{Small,Medium,Large};
enum apple{Small,Medium,Large};//!!!Error
以上是错误的
于是可用作用域内枚举
enum class egg{Small,Medium,Large};
enum class apple{Small,Medium,Large};
或者:
enum struct egg{Small,Medium,Large};
enum struct apple{Small,Medium,Large};
使用时必须用枚举名来限定枚举量
egg ch = egg::Large;
有些情况下,常规枚举将自动转换为 整型。但作用域内枚举将不会隐式转换为整型(可显式)
作用域内枚举底层类型,c++11默认为int,可自定义:
enum class:short pizza{Small,Medium,Large};
注意:
类声明中的枚举,在外界要用的时候还要有类的作用域解析符。
三,静态常量
class Stack
{
private:
static const int MAX=10;
...
public:
...
};
静态成员变量
无论创建了多少类对象,都只有一个静态成员副本。
注意:但不能在类声明中初始化静态成员变量。因为类声明不分配内存
对于静态类成员变量可以再类声明之外用单独的语句来进行初始化
初始化语句放在类的实现文件(.cpp)文件中
初始化语句指明类型并用了作用域解析符,但没有使用关键字 static
Stack.h
class Stack
{
private:
static int kk;
…
public:
…
};
…
Stack.cpp
int Stack::kk = 12;
静态成员函数
抽象数据类型
Stack.h
#ifndef STACKER_H_
#define STACKER_H_
typedef long int Item;
class Stack
{
private:
enum {MAX=10};
Item items[MAX];
int top;
public:
bool pop(Item &im);
bool push(const Item &im);
bool IsFull() const;
bool IsEmpty() const;
Stack();
};
#endif
Stack.cpp
#include"Stack.h"
#include
Stack::Stack()
{
top=0;
}
bool Stack::pop(Item& im)
{
if(!IsEmpty()){
im=items[top-1];
top--;
return true;
}
else return false;
}
bool Stack::push(const Item& im)
{
if(!IsFull()){
items[top]=im;
top++;
return true;
}
else return false;
}
bool Stack::IsEmpty() const
{
/*if(top==MAX-1)
return true;
else return false;*/
return top==0;
}
bool Stack::IsFull() const
{
return top==MAX-1;
}
测试程序(main.cpp)
#include
#include
#include"Stack.h"
int main(int argc, char** argv) {
using namespace std;
Stack st;
enum col{MAX=10
};
char ch;
long po;
cout<<"Please enter A to add a purchase order.\n"
<<"P to process a PO, or Q to quit\n";
while(cin>>ch && toupper(ch)!='Q')
{
while(cin.get()!='\n');
if(!isalpha(ch))
{
cout<<'\a';
continue;
}
switch(ch)
{
case 'A':
case 'a':
cout<<"Enter a PO number to add: ";
cin>>po;
if(st.IsFull())
cout<<"stack already full\n";
else st.push(po);
break;
case 'p':
case 'P':
if(st.IsEmpty())
cout<<"stack already Empty\n";
else {
st.pop(po);
cout<<"PO # "<
运算符重载
Time.h
#ifndef TIME_H_
#define TIME_H_
class Time
{
private:
long int m_time;
int m_hours;
int m_minutes;
int m_seconds;
public:
Time();
Time(const long <ime);
~Time();
void ShowTime() const;
void SetTime(const long& ltime);
Time operator+(const Time& T) const;
Time Time::operator+(const long int& a) const;
};
#endif
time.cpp
#include
#include"Time.h"
using std::cout;
using std::cin;
using std::endl;
Time::Time()
{
m_time=0;
m_hours=0;
m_minutes=0;
m_seconds=0;
}
Time::Time(const long<ime)
{
m_time=ltime;
m_hours=ltime/3600;
m_minutes=(ltime%3600)/60;
m_seconds=ltime%60;
}
void Time::SetTime(const long<ime)
{
m_time=ltime;
m_hours=ltime/3600;
m_minutes=(ltime%3600)/60;
m_seconds=ltime%60;
}
Time::~Time()
{
cout<<"The last time is :\n";
ShowTime();
cout<<" Bye~ \n";
}
void Time::ShowTime() const
{
cout<
友元
友元有三种:
——赋予其访问类私有成员的权限
为什么使用友元函数?对于重载操作符时,有时两个参数不是同一类,这导致两个参数放左右两边效果不一样,因此可以将重载弄成非成员函数,这时为了方便访问其私有变量,可用友元函数
Time T1(12);
long int a = 24;
T1 = T1 + a;
创建友元之后才能:
T1 = a + T1;
对于非成员函数的重载运算符函数,比成员函数时多一个参数。
创建友元 friend
在要成为其友元的类声明中声明友元函数的原型,并在前面加关键字 friend
friend Time operator+(const long int ti,const Time& T);
注意:定义友元函数时就不用关键字 friend 了
常用友元:重载 <<
class Time
{
private:
...
public:
...
friend std::ostream& operator<<(const std::ostream& os,
const time tm);
};
例:Vector
Vector.h
#ifndef VECTOR_H_
#define VECTOR_H_
namespace VECTOR{
class Vector
{
public:
enum Mode{RECT,POL};
private:
double m_x;
double m_y;
double m_ang;
double m_mag;
Mode m_mode;
public:
//Vector
Vector();
Vector(const double n1,const double n2,const Mode mode=RECT);
~Vector();
//Set Vector
void SetX(const double x);
void SetY(const double y);
void ReSet(const double n1,const double n2,const Mode mode=RECT);
void SetAng(const double ang);
void SetMag(const double mag);
void SetMode(const Mode mode=RECT){m_mode=mode;};
//GetVector
double GetX()const {return m_x;}
double GetY()const {return m_y;}
double GetAng()const {return m_ang;}
double GetMag()const {return m_mag;}
Mode GetMode()const {return m_mode;}
//operator
Vector operator+(const Vector& v) const;
Vector operator-(const Vector& v) const;
Vector operator-() const;//重载减法运算符,两个参数为减,一个参数为负
//friend functions
friend Vector operator+(const double& d,const Vector& v);
friend Vector operator-(const double& d,const Vector& v);
friend Vector operator*(const double& d,const Vector& v);
friend std::ostream & operator<<(std::ostream &os,const Vector& v);
};
}
#endif
Vector.cpp
#include
#include
#include"Vector.h"
using std::cout;
using std::cin;
using std::endl;
using std::ostream;
using std::sqrt;
namespace VECTOR
{
//Vector
Vector::Vector()
{
m_x=0;
m_y=0;
m_ang=0;
m_mag=0;
m_mode=RECT;
}
Vector::Vector(const double n1,const double n2,const Mode mode)
{
m_mode=mode;
if(mode==RECT)
{
m_x=n1;
m_y=n2;
m_mag=sqrt(m_x*m_x+m_y*m_y);
if(m_y==0.0 && m_x==0.0)
m_ang=0;
else
m_ang=atan2(m_y,m_x);
}
else if(mode==POL)
{
m_ang=n1;
m_mag=n2;
m_x=m_mag*cos(m_ang);
m_y=m_mag*sin(m_ang);
}
else
{
cout<<"Incorrect 3rd argument to Vector() - - "
<<"Vector set to 0\n";
m_x=0;
m_y=0;
m_ang=0;
m_mag=0;
m_mode=RECT;
}
}
Vector::~Vector()
{
cout<<"Bye~"<
类与动态内存
技巧:
例程(String1 P442)
//String1.h
#ifndef STRING1_H_
#define STRING1_H_
#include
using std::ostream;
using std::istream;
class String
{
private:
char *str;
int len;
static int num_strings;
static const int CINLIM = 80;
public:
//构造,析构,普通成员函数
String(const char * st);
String();
String(const String & st);
~String();
int length() const {return len;}
//重载
String & operator=(const char *);
String & operator=(const String &);
char & operator[](int i);
const char operator[](int i) const;//返回const变量,不能用于左值
//友元
friend bool operator==(const String &st1,const String &st2);
friend bool operator>(const String &st1,const String &st2);
friend bool operator<(const String &st1,const String &st2);
//以上三个友元的好处是,比起成员函数,可以将String对象与C字符串比较,因为const char*构造函数
friend ostream& operator<<(ostream &os,const String &st);
friend istream& operator>>(istream &is,String &st);
static int HowMany();
};
#endif
//String1.cpp
#include
#include"string1.h"
using std::cout;
using std::cin;
//初始化静态成员
int String::num_strings=0;
int String::HowMany()
{
return num_strings;
}
//类方法定义
String::String()
{
len=0;//书上是4
str = new char[1];
str[0]='\0';
num_strings++;
}
String::String(const char * st)
{
len=std::strlen(st);
str = new char[len+1];
std::strcpy(str,st);
num_strings++;
}
String::String(const String & st)
{
len=st.len;
str = new char[len+1];
std::strcpy(str,st.str);
num_strings++;
}
String::~String()
{
--num_strings;
delete[]str;
}
String &String::operator=(const String& st)
{
if(this == & st)
return *this;
delete[]str;//记得更换内容时删除原空间
len= st.length();
str=new char[len+1];
std::strcpy(str,st.str);
return * this;
}
String &String::operator=(const char * st)
{
delete[]str;//记得更换内容时删除原空间
len=std::strlen(st);
str = new char[len+1];
std::strcpy(str,st);
return *this;
}
char & String::operator[](int i)
{
return str[i];
}
const char String::operator[](int i) const
{
return str[i];
}
bool operator>(const String & st1,const String & st2)
{
return (std::strcmp(st1.str,st2.str)>0);
}
bool operator<(const String & st1,const String & st2)
{
return (std::strcmp(st1.str,st2.str)<0);
}
bool operator==(const String & st1,const String & st2)
{
return (std::strcmp(st1.str,st2.str)==0);
}
ostream & operator<<(ostream & os,const String &st)
{
os<>(istream & is,String &st)
{
char temp[String::CINLIM];
is.get(temp,String::CINLIM);
if(is)
st=temp;
while(is && is.get()!='\n')
continue;
//假定输入字符不多于String::CINLIM,并丢弃多余字符
//在if条件下,若由于某种原因(如到达文件尾或is.get(char*,int)读取是空行)导致输入失败,istream对象值被设为False
return is;
}
更高效——返回对象的引用
注意:参数是 const引用,而返回参数对象引用时必须也是const
再谈 定位 new 运算符
delete 不能与定位运算符配合使用 !
对于 定位new 分配内存的类对象,只能显式调用析构函数。
class fruit
{
…
};
…
char * buffer = new char[40];
…
fruit * fp = new (buffer) fruit;
…
delete fp;//错误!
delete[] buffer;//可以,但是不会调用 fruit 的析构函数
类中嵌套结构,类声明
is-a关系 (公有继承)
公有继承 is-a
class fruit
{
private:
int weight;
int prince;
public:
fruit();
fruit(const int w,const int p);
bool SetWeight(int w);
int GetWeight();
...
};
...
class apple:public fruit
{
...
};
2.使用派生类
关于继承成员的属性变化:
基类方法的调用
派生类中,对于需要重定义的方法,使用作用域解析运算符来调用基类方法(不需重定义的方法就不需要作用域解析符)。否则:
void BrassPlus::ViewAcct() const
{…
ViewAcct();//Error!造成无限递归
}
protected成员
注意:
关于基类指针和引用(多态):
向上强制转换:
注意:
公有继承的关系:is-a 关系
在派生类中重写继承而来的部分函数(多态)
注意:重定义的方法与基类方法参数特征标不同时,并不会产生两个重载版本,而是派生类中新定义的方法隐藏了基类版本。
=> 两条经验:
一:直接重写
二:虚函数virtual
创建方式:
特性:
了解:由于虚函数,对于同名函数的调用需要在运行时具体确定,被称为——动态联编
(一般是静态联编)
使用:
抽象基类ABC(纯虚函数)
继承和动态内存分配
情况一:基类使用new 而派生类不用new
使用动态内存分配的基类来说,需要注意特殊方法:析构函数,复制构造函数和重载赋值函数
若派生类新增部分不需要new 则这三个特殊方法也不需要显式定义
派生类与基类的这种关系也适用于 本身是对象的类成员。
情况二:派生类使用new
class baseDMA
{
private:
char * label;
int rating;
public:
baseDMA(const char * l="null",int r=0);
baseDMA(const baseDMA & rs);
virtual ~baseDMA();
baseDMA & operator=(const baseDMA & rs);
...
};
...
class hasMDA:public baseDMA
{
private:
char * style;
public:
hasDMA(const char * l="null",int r=0);
hasDMA(const hasDMA & rs);
~ hasDMA();
hasDMA & operator=(const hasDMA & rs);
}
需要注意特殊方法:析构函数,复制构造函数和重载赋值函数 都必须显式定义
析构函数:
只需负责派生类新增部分的处理,派生类的析构执行完后自动调用基类的析构函数
baseDMA::~baseDMA()
{
delete[]label;
}
hasDMA::~hasDMA()
{
delete[]style;
}
复制构造函数:
baseDMA::baseDMA(const baseDMA & rs)
{
label = new char[std::strlen(rs.label)+1];
std::strcpy(label,rs.label);
rating=rs.rating;
}
hasDMA::hasDMA(const hasDMA & hs):baseDMA(hs)
{
style = new char[std::strlen(hs.style)+1];
std::strcpy(style,hs.style);
}
赋值运算符重载函数:
派生类的赋值运算符重载函数属于同名函数的重新定义,因而会时基类的对应函数隐藏,因而需要显式调用基类的该函数,完成全部复制
//基类的赋值运算符重载函数:
baseDMA & baseDMA::operator=(const baseDMA & rs)
{
if(this == &rs)
return *this;
delete[]label;
label = new char[std::strlen(rs.label)+1];
std::strcpy(label,rs.label);
rating = rs.rating;
return *this;
}
//派生类的赋值运算符重载函数:
hasDMA & hasDMA::operator=(const hasDMA & hs)
{
if(this == &hs)
return *this;
baseDMA::operator=(hs);
delete[]style;
label = new char[std::strlen(rs.style)+1];
std::strcpy(style,rs.style);
return *this;
}
访问基类的友元
将派生类对象强制类型转换可得到基类对象
class baseDMA
{
private:
char * label;
int rating;
public:
friend std::ostream & operator<<(std::ostream &os,const baseDMA* rs);
...
};
...
class hasMDA:public baseDMA
{
private:
char * style;
public:
friend std::ostream & operator<<(std::ostream &os,const hasDMA* hs);
...
}
对于friend std::ostream & operator<<(std::ostream &os,const hasDMA* hs); 此函数并不是基类的友元,所以无法访问基类的 label 和 rating 等私有成员,所以可以通过显式类型转换调用基类的友元函数:
friend std::ostream & operator<<(std::ostream &os,const hasDMA* hs)
{
os<<(const baseDMA &)hs;
os<<"Style: "<
has-a关系(包含对象成员,保护和私有继承)
has-a 关系的特点:不继承接口
is-a 关系的特点:继承接口,不一定继承实现(纯虚函数)
模板类——valarray
—— 使用动态内存分配
声明对象
valarray q_values;
valarray weights;
2.初始化
默认:长度为0的空数组
一个参数的(n)构造:长度为n的数组
两个参数(a,n)的构造:长度为n,且各位被初始化为a 的数组
两个参数(p,n)的构造(第一个参数是数组,第二个整型),长度为n,且初始化为数组p的前n个
初始化列表(C++11):
double gpa[5]={3.1,4.2,5.6,12.4,16.8};
valarray v1;
valarray v2(8);//an array of 8 int elements
valarray v2(10,8);//an array of 8 int elements,each set to 10
valarray v4(gpa,4);//an array of 4 double elements,set to {3.1,4.2,5.6,12.4}
valarray v5 = {1,2,3,4,5};//C++11
包含对象成员
class Student
{
private:
string name;
valarray scores;
...
};
初始化:
Student::Student(const char* str,const double * pd,int n)
:name(str),scores(pd,n){}
私有继承
继承成员的属性变化
私有继承的声明
注意:不写关键字private,默认继承方式也是私有继承
class Student:private std::string,private std::valarray
{
...
}
私有继承的构造(初始化)
Student::Student(const char* str,const double * pd,int n)
:std::string(str),std::scores(pd,n){}
基类方法的调用
必须使用 类名+作用域解析符
访问基类对象
将派生类对象强制类型转换可得到基类对象
//相当于得到上面包含对象版本的name对象成员
const string& Student::Name const
{
return (const string &)*this;
}
访问基类的友元
ostrean & operator<<(ostream &os,const Student & stu)
{
os<<"Scores for"<<(const String & )stu<<":\n";
...
}
私有继承,还是 包含对象成员?
两者区别在于:
两者优势:
保护继承
类继承总结
类继承属性转换总结
特征 公有继承 保护继承 私有继承
基类的公有成员变成 派生类的公有成员 派生类的保护成员 派生类的私有成员
基类的保护成员变成 派生类的保护成员 派生类的保护成员 派生类的私有成员
基类的私有成员变成 只能同基类接口访问(私私有) 只能同基类接口访问(私私有) 只能同基类接口访问(私私有)
能否隐式向上转换 能 只在派生类中能 不能
类继承访问权限的更改
在私有/保护继承中,若想让基类的接口也成为了派生类的接口,则可以:
方法一:再定义一个调用基类方法的接口
double Student::sum() const
{
return std::valarray::sum();
}
方法二:使用 using
用 using 指出派生类可以使用特点的基类成员,即使是私有派生
class Student:private std::string,private std::valarray
{
...
public:
using std::valarray::sum;
...
}
则 sum 就像 Student 的公有方法一样调用即可
Student ada("Alice",{90.2,96.3,94.8},3);
cout<
注意:
方法三(老式,即将被摒弃) 重新声明基类方法
class Student:private std::string,private std::valarray
{
...
public:
std::valarray::operator[];
...
}
像不使用using 关键字的 using声明。
多重继承MI
有多个直接基类的类,每个类都要写明继承方式
class Worker
{
string name;
int ID;
public:
...
};
class Singer:public Worker
{
int voice;
...
};
class Waiter:public Worker
{
int panache;//译:神气,气质
};
class SingerWaiter:public Singer,public Waiter
{
...
};
第一代基类有几个?
SingerWaiter ed;
Worker * pr = &ed;//!!!Error
//SingerWaiter对象中包含两个第一代基类Worker,Worker指针无法确定指向哪一个
//解决方法:强制类型转化
worker * pr = (Waiter *)&ed;
事实上,并不需要多个第一代基类 worker 存在于 SingerWaiter 中,所以可以采用——虚基类
虚基类
虚基类 使得从多个类(这多个类的基类相同)派生出的对象只继承一个基类对象
创建虚基类
在类声明(继承部分)使用关键字 virtual 。
class Singer:virtual public Worker{...};
class Waiter:public virtual Worker{...};
//virtual 与 public 的顺序无影响
class SingerWaiter:public Singer,public Waiter{...};
虚基类特性
使 SingerWaiter 继承的 Waiter 和 Singer 类共享同一个 Worker基类
=> SingerWaiter 只包含一个 Worker 子对象
虚基类相关的构造函数
SingerWaiter::SingerWaiter(const Worker &wk,int p=0,int v=Singer::Other):Waiter(wk,p),Singer(wk,v){}//!!!Error
虚基类不允许通过中间类的方式调用构造函数,因为有两种途径调用基类的构造,所以上述代码中,wk 并不会传递给两个子对象。
解决方式是:显式调用虚基类的构造函数:
SingerWaiter::SingerWaiter(const Worker &wk,int p=0,int v=Singer::Other):Worker(wk),Waiter(wk,p),Singer(wk,v){}
对于虚基类上述方法正确,对于非虚基类则是非法的。
使用哪个方法?
若想通过派生类调用继承而来的同名函数(派生类并未重新定义该函数),可能导致二义性。
SingerWaiter newhire("Elis Hwaks",2005,6,Other);
newhire.show();//!!!ambigious
//解决方式:用类名+作用域解析符:
newhire.Singer::show();
模块化设计+私有/保护辅助方法,见P560
虚基类与非虚基类混合使用:
则最终派生类中,包含一个表示所有的虚途径的基类子对象和分别表示各条非虚途径的多个基类子对象0
虚二义性(成员名的优先)
类模板
定义类模板
template//template 也行
class Stack
{
private:
enum {MAX=10};
Type items{MAX};
int top;
public:
Stack();
bool isempty() const;
bool isfull() const;
bool push(const Type &item);
bool pop(Type & item);
};
template
Stack::Stack()
{
top=0;
}
template
bool Stack::isempty()
{
return top==0;
}
...
使用模板类
仅在程序中包含模板并不能生成模板类,而必须请求实例化
Stack kernels;
Stack coloels;
注意:类模板使用时,必须显式提供类型。而模板函数,编译器则可根据参数类型自行判断。
深入讨论模板类
指针栈:
类型为指针的一种栈,无论将指针视为纯指针,数组名还是new得到一段空间 都是不行的(P572,P573)。正确使用指针栈的方法是:让调用程序提供一个指针数组,不同的指针指向不同的内存,这样吧指针放在栈中才是有意义的。
注意:创建不同指针是调用程序的职责而不是栈的职责,栈只负责管理指针,不负责创建指针。
例子stcktp1 (P574,575,576)(待添加)
数组模板示例和非类型参数
允许指定数组大小的简单数组模板:方法一是:在类中使用动态数组和构造函数参数来提供元素数组;另一方法是:使用模板参数来提供常规数组的大小(c++11新增的模板array就是这样),下面示例将演示
///arrattp.h
#ifndef ARRAYTP_H_
#define ARRAYTP_H_
#include
#include
template
class ArrayTp
{
private:
T ar[n];
public:
ArrayTp(){};
explicit ArrayTp(const T & v);
virtual T& operator[](int i);
virtual T operator[](int i) const;
};
template
ArrayTp::ArrayTp(const T&v)
{
for(int i=0;i
T & Array::operator[](int i)
{
if(i<0 || i>=n)
{
std::cout<<"Error in array limits:"<
T Array::operator[](int i) const
{
if(i<0 || i>=n)
{
std::cout<<"Error in array limits:"<
使用:
ArrayTp eggweights;
与动态内存分配的方法相比,其缺点是:
模板多功能性
1.可用作基类,组件类,其他模板类的类型参数
2.递归用法:
ArrayTp< ArrayTp,10 > twodee;
类似于 int two[10][5]
注意:> >(中间有空格) 与 >> 区分开来 (C++11就不用了)
3.使用多个模板参数
4.默认类型模板参数
template class Topo {...};
虽然可以给类模板提供默认类型,但不能给模板函数提供。而非类型参数则两者都能提供默认值。
模板的具体化
隐式实例化
编译器只在需要类对象时创建对应的类定义,并生成对应的对象
注:只定义对象指针不new不算“对象创建”
显式实例化
关键字 template :
//!!!该声明必须位于模板定义所在的名称空间中
template class ArrayTp;
显式具体化
对特殊类型进行特殊具体化
template <> class ArraySort{...};
//早期:
class ArraySort{...};
部分具体化
部分限制模板的通用性
//general template
template
//specialization with T2 set to int
template class Pair
//specialization with T1,T2 set to int,相当于显式具体化
template <> class Pair
若有多个模板类可供选择,则优先具体化程度最高的
指针提供特殊版本来部分具体化现有模板
template
class Feeb{…};
template
class Feeb{…};
部分具体化特性能提供各种限制
//general template
template
//specialization with T3 set to T2
template
//specialization with T1,T2 set to T1*
template class Tiro
成员模板
template
class bete
{
private:
template //模板类
class hold
{
...
}
hold q;
hold g;
public:
template
U blab(U u,T t){...}//模板函数
...
}
可以在类声明中只声明模板类和模板函数,而在类外定义:
template
class beta
{
private:
template //模板类
class hold;
hold q;
hold g;
public:
template
U blab(U u,T t);//模板函数
...
}
template
template
class beta::hold
{
...
};
template
template
U beta::blab(U u,T t)
{
...
}
将模板用作参数
模板还可以包含本身就是模板的参数
template class Thing>
class Crab
{...};
//若有:
template
class King{...};
//则可以:
Crab legs;
模板类和友元
模板的友元分三类:
模板类的非模板友元函数
template
class HasFriend
{
public:
friend void counts();
...
};
注意,不能 friend void report(HasFriend &); 因为并不存在 HasFriend 这个类,而只有特定的具体化,要提供模板类参数,必须指明具体化,可以这样:
template
class HasFriend
{
public:
friend void report(HasFriend &);
...
};
...
HasFriend hf1;
HasFriend hf1;
//注意:report本身并不是模板函数,而是只能使用一个模板作为参数,这意味着必须要为使用的友元显式具体化:
void report(HasFriend &);
void report(HasFriend &);
模板类的约束模板友元函数
三步:
1.再类定义前声明每个模板函数:
template void counts();
template void report(T &);
2.在模板类中再次将模板声明为友元:
template
class HasFriend
{
public:
friend void counts();
friend void report<>(HasFriend &);
…
};
//!!!在声明时模板具体化,对于report,<>可以空,因为可根据参数判断出模板类型参数为 HasFriend
//当然,也可以:friend void report
//一种具体化类只对应一种友元函数
3.为友元函数提供模板定义
模板类的非约束模板友元函数
每个友元的具体化都能访问所有的具体化类。
template
class ManyFriend
{
public:
templatefriend void show2(C &,D &);
...
};
templatefriend void show2(C &,D &)
{
...
}
...
Manyfriend hfi;
Manyfriend hfdb;
show2(hfi,hfdb);
模板别名
typedef 语句
typedef std::array<double,12> arrd;
arrd gallons;//gallons的类型是:std::array
using= 语句(C++11)
template<typename T>
using arrtype = std::array<T,12>;
...//arrtype 就表示 std::array
arrtype<double> gallons;//gallons的类型是:std::array
arrtype<int> days;//days的类型是:std::array
C++11 允许 语法 using= 用于非模板,非模板中该语法与常规 typedef 一样
typedef const char* pc1;
//<=>
using pc2 = const char*;
typedef const int * (*pa1)[10];//我认为这是个指向一个指针数组的指针
//<=>
using pa2 = const int * (*)[10];
C++新增:可变参数模板(待补充,18章)
接受可变数量的参数的模板类或模板函数