类
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<iostream>
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 <iostream>
#include<cctype>
#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 # "<<po<<" poped\n";
}
break;
}
cout<<"Please enter A to add a purchase order.\n"
<<"P to process a PO, or Q to quit\n";
}
cout<<"Bye\n";
return 0;
}
运算符重载
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<iostream>
#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<<m_hours<<" (h): "<<m_minutes<<" (m): "
<<m_seconds<<" (s):\n";
}
Time Time::operator+(const Time& T) const
{
return Time(m_time+T.m_time);
}
Time Time::operator+(const long int& a) const
{
return Time(m_time+a);
}
友元
友元有三种:
——赋予其访问类私有成员的权限
为什么使用友元函数?对于重载操作符时,有时两个参数不是同一类,这导致两个参数放左右两边效果不一样,因此可以将重载弄成非成员函数,这时为了方便访问其私有变量,可用友元函数
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<iostream>
#include<cmath>
#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~"<<endl;
}
//Set Vector
void Vector::SetX(const double x)
{
m_x=x;
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);
}
void Vector::SetY(const double y)
{
m_y=y;
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);
}
void Vector::ReSet(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;
}
}
void Vector::SetAng(const double ang)
{
m_ang=ang;
m_x=m_mag*cos(m_ang);
m_y=m_mag*sin(m_ang);
}
void Vector::SetMag(const double mag)
{
m_mag=mag;
m_x=m_mag*cos(m_ang);
m_y=m_mag*sin(m_ang);
}
//operator
Vector Vector::operator+(const Vector& v) const
{
return Vector(v.m_x+m_x,v.m_y+m_y,RECT);
}
Vector Vector::operator-(const Vector& v) const
{
return Vector(m_x-v.m_x,m_y-v.m_y,RECT);
}
Vector Vector::operator-() const//重载减法运算符,两个参数为减,一个参数为负
{
return Vector(-m_x,-m_y,RECT);
}
//friend functions
Vector operator+(const double& d,const Vector& v)
{
return Vector(d+v.m_x,v.m_y);
}
Vector operator-(const double& d,const Vector& v)
{
return Vector(v.m_x-d,v.m_y);
}
Vector operator*(const double& d,const Vector& v)
{
return Vector(v.m_x*d,v.m_y*d);
}
ostream & operator<<(ostream &os,const Vector& v)
{
if(v.m_mode==Vector::RECT)
{
os<<v.m_x<<" + "<<v.m_y<<"i \n";
}
else
{
os<<v.m_mag<<"∠"<<v.m_ang<<endl;
}
}
}
类与动态内存
技巧:
例程(String1 P442)
//String1.h
#ifndef STRING1_H_
#define STRING1_H_
#include<iostream>
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<cstring>
#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<<st.str;
return os;
}
istream & operator>>(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: "<<hs.style<<endl;
return os;
}
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<double> 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<double>
{
...
}
私有继承的构造(初始化)
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<double>::sum();
}
方法二:使用 using
用 using 指出派生类可以使用特点的基类成员,即使是私有派生
class Student:private std::string,private std::valarray<double>
{
...
public:
using std::valarray<double>::sum;
...
}
则 sum 就像 Student 的公有方法一样调用即可
Student ada("Alice",{90.2,96.3,94.8},3);
cout<<ada.sum();
注意:
方法三(老式,即将被摒弃) 重新声明基类方法
class Student:private std::string,private std::valarray<double>
{
...
public:
std::valarray<double>::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<class Type>//template <typename Type>也行
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 <class Type>
Stack<Type>::Stack()
{
top=0;
}
template <class Type>
bool Stack<Type>::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<iostream>
#include<csdlib>
template<class T,int n>
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 <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 & Array<T,n>::operator[](int i)
{
if(i<0 || i>=n)
{
std::cout<<"Error in array limits:"<<i
<<"is out of range\n";
std::exit(EXIT_FAILURE);
}
return ai[i];
}
template <class T,int n>
T Array<T,n>::operator[](int i) const
{
if(i<0 || i>=n)
{
std::cout<<"Error in array limits:"<<i
<<"is out of range\n";
std::exit(EXIT_FAILURE);
}
return ai[i];
}
#endif
使用:
ArrayTp<double,12> eggweights;
与动态内存分配的方法相比,其缺点是:
模板多功能性
1.可用作基类,组件类,其他模板类的类型参数
2.递归用法:
ArrayTp< ArrayTp<int,5>,10 > twodee;
类似于 int two[10][5]
注意:> >(中间有空格) 与 >> 区分开来 (C++11就不用了)
3.使用多个模板参数
4.默认类型模板参数
template <class T1,class T2 = int> class Topo {...};
虽然可以给类模板提供默认类型,但不能给模板函数提供。而非类型参数则两者都能提供默认值。
模板的具体化
隐式实例化
编译器只在需要类对象时创建对应的类定义,并生成对应的对象
注:只定义对象指针不new不算“对象创建”
显式实例化
关键字 template :
//!!!该声明必须位于模板定义所在的名称空间中
template class ArrayTp<string,100>;
显式具体化
对特殊类型进行特殊具体化
template <> class ArraySort<const char*>{...};
//早期:
class ArraySort<const char*>{...};
部分具体化
部分限制模板的通用性
//general template
template <class T1,class T2> class Pair{…};
//specialization with T2 set to int
template class Pair<T1,int>{…};
//specialization with T1,T2 set to int,相当于显式具体化
template <> class Pair<int,int>{…};
若有多个模板类可供选择,则优先具体化程度最高的
指针提供特殊版本来部分具体化现有模板
template
class Feeb{…};
template<class T*>
class Feeb{…};
部分具体化特性能提供各种限制
//general template
template <class T1,class T2,class T3> class Tiro{…};
//specialization with T3 set to T2
template <class T1,class T2> class Tiro<T1,T2,T2>{…};
//specialization with T1,T2 set to T1*
template class Tiro<T1,T1*,T1*>{…};
成员模板
template <typename T>
class bete
{
private:
template <typename V>//模板类
class hold
{
...
}
hold<T> q;
hold<int> g;
public:
template<typename U>
U blab(U u,T t){...}//模板函数
...
}
可以在类声明中只声明模板类和模板函数,而在类外定义:
template <typename T>
class beta
{
private:
template <typename V>//模板类
class hold;
hold<T> q;
hold<int> g;
public:
template<typename U>
U blab(U u,T t);//模板函数
...
}
template <typename T>
template <typename V>
class beta<T>::hold
{
...
};
template <typename T>
template <typename U>
U beta<T>::blab(U u,T t)
{
...
}
将模板用作参数
模板还可以包含本身就是模板的参数
template <template <typename T> class Thing>
class Crab
{...};
//若有:
template <typename T>
class King{...};
//则可以:
Crab<King> legs;
模板类和友元
模板的友元分三类:
模板类的非模板友元函数
template<class T>
class HasFriend
{
public:
friend void counts();
...
};
注意,不能 friend void report(HasFriend &); 因为并不存在 HasFriend 这个类,而只有特定的具体化,要提供模板类参数,必须指明具体化,可以这样:
template<class T>
class HasFriend
{
public:
friend void report(HasFriend<T> &);
...
};
...
HasFriend<short> hf1;
HasFriend<int> hf1;
//注意:report本身并不是模板函数,而是只能使用一个模板作为参数,这意味着必须要为使用的友元显式具体化:
void report(HasFriend<short> &);
void report(HasFriend<int > &);
模板类的约束模板友元函数
三步:
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<HasFriend >(HasFriend &);
//一种具体化类只对应一种友元函数
3.为友元函数提供模板定义
模板类的非约束模板友元函数
每个友元的具体化都能访问所有的具体化类。
template<class T>
class ManyFriend
{
public:
template<typename C,typename D>friend void show2(C &,D &);
...
};
template<typename C,typename D>friend void show2(C &,D &)
{
...
}
...
Manyfriend<int> hfi;
Manyfriend<double> hfdb;
show2(hfi,hfdb);
模板别名
typedef 语句
typedef std::array<double,12> arrd;
arrd gallons;//gallons的类型是:std::array<double,12>
using= 语句(C++11)
template<typename T>
using arrtype = std::array<T,12>;
...//arrtype<T> 就表示 std::array<T,12>
arrtype<double> gallons;//gallons的类型是:std::array<double,12>
arrtype<int> days;//days的类型是:std::array<int,12>
C++11 允许 语法 using= 用于非模板,非模板中该语法与常规 typedef 一样
typedef const char* pc1;
//<=>
using pc2 = const char*;
typedef const int * (*pa1)[10];//我认为这是个指向一个指针数组的指针
//<=>
using pa2 = const int * (*)[10];
C++新增:可变参数模板(待补充,18章)
接受可变数量的参数的模板类或模板函数
bbq:for(var j=0;j<a.length;j++){ ccc: for(var i =0;i<a.length;i++){ if( i==5 ){ break bbq; //直接跳出bbq外层循环 } }}或者:function testFor() { bbq: for(var k=0;k<a.length;k++){ console.log('444'.
1.widget.h#ifndef WIDGET_H#define WIDGET_H#include #include #include #include #include #include #include class MyMainWindow : public QWidget{ Q_OBJECTpublic: MyMainWindow(QWidg_c++ qt movie
首先确保你的笔记本电脑连接的是宽带。不是无线网络。好了,我们开始!1、以管理员的方式打开命令提示符,输入cmd命令,回车。2、在cmd命令中输入:netsh wlan set hostednetwork mode =allow 会出现承载网络模式已设置为允许3、在cmd命令中输入:netsh wlan set hostednetwork ssid=您想要的无线网络的名称 _笔记本怎么建虚拟网络连接
文章目录一、概率图模型1.1 概览1.2 有向图1.3 无向图1.4 生成式模型和判别式模型1.4.1生成式模型和判别式模型区别1.4.2 为啥判别式模型预测效果更好二、隐式马尔科夫模型HMM2.1 HMM定义2.2 HMM三要素和两个基本假设2.3 HMM三个基本问题2.4 HMM基本解法2.4.1 极大似然估计(根据I和O求λ)2.4.2 前向后向算法(没有I)2.4.3 序列标注(解码)过程三、最大熵马尔科夫MEMM模型3.1 MEMM原理和区别3.2 标注偏置四、条件随机场CRF4.1 CRF定义4_hmm 和 crf推导
zabbix在编译时报:configure: error: Invalid Net-SNMP directory - unable to find net-snmp-config解决办法是安装net-snmp-develyum install -y net-snmp-devel_configure: error: invalid net-snmp directory - unable to find net-snmp-confi
int整型: .net中特指Int32为32位长度符号整型变量float:单精度浮点数32位长度1位符号位8位指数位与23位数据位 .net中又称为Singledouble:64位长度双精度浮点数1位符号位11位指数位52位数据位它们互相关系就:int可以稳式转换成float和double,float只能强制转换成int但可以隐式转换成double,double只能强制转换
[问题描述]数据库服务器非法断电,重新启动出现以下错误:ORA-01122: 数据库文件 1 验证失败ORA-01110: 数据文件 1: ‘D:\ORADATA\ORCL\SYSTEM01.DBF’ORA-01207: 文件比控制文件更新 - 旧的控制文件[问题分析]通过脚本检查发现数据库控制文件比数据文件旧引起的。在数据库运行期间,由于检查点发生等原因会不断的更新控制文件,同时数据..._控制文件信息过旧
以上面的配置块为例,我们要在nginx运行时获取内置变量$arg_key的值,$arg_keyA内置变量表示在http的get请求时的参数名为key的参数,比如请求http://127.0.0.1:8881/wyx?keyA=42,我们要通过ngx_http_compile_complex_value和ngx_http_complex_value来获取keyA的值...,如何通过自定义变量来获取这个值呢_ngx_http_complex_value
使用直方图相似性合并相似区域#include<opencv2/imgproc/imgproc.hpp>#include<opencv2/highgui/highgui.hpp>#include<opencv2/core/core.hpp>#include<vector>#include<iostream>#include&...
1作用域,与C系列语言不一样的地方:举几个小例子:a=100def func1(a): a=200func1(a)print(a) # 这个是100,因为传参是a,被认定是局部变量def func2(): a=200func2()print(a)# 很遗憾这个py中也是100,a被声明在func里面,被认定为局部变量,并不是全局变量的a,# 只要局部有a,离这个a更远的所有的a在局部a的作用域内都被屏蔽掉。for i in range(2): a=2_"a = \"100\"def func(): a = \"200\"func()print(a)a"
mips64平台下Qt5.15.2源码编译源码下载依赖安装configure参数编译安装源码下载下载地址:https://download.qt.io/archive/qt/5.15/5.15.2/single/qt-everywhere-src-5.15.2.tar.xz依赖安装参考:https://doc.qt.io/qt-5/linux-requirements.htmlconfigure参数这个可以根据自己需要调整需要编译的模块,我的配置是这样的:./configure -prefix_mips64 qt5
/*********************************************************************************************************** uC/OS-II*..._渭cos stk_size