类模板

作者: Vincy_ivy | 来源:发表于2019-05-31 21:38 被阅读0次

题目

求范围2~n中的质数,n在程序运行时由键盘输入


#include<bits/stdc++.h>  //直接访问群体——数组类
using namespace std;

template<class T>
class Array{
private:
    T *list;
    int size;
public:
    Array(int sz=50);//构造函数 
    Array(const Array<T>&a);//复制构造函数 
    ~Array();//析构函数
    Array<T>&operator=(const Array<T>&rhs);//重载“=”,对象可以整体赋值
    T &operator[](int i);//重载[],使Array对象可以起到C++普通数组的作用 
    const T & operator [](int i)const;//[]运算针堆const的重载
    operator T*();//重载到T*类型的转换,使Array对象可以起到C++数组的作用
    operator const T*() const;//到T*类型转换操作符针堆const的重载
    int getSize() const;
    void resize(int sz);
};

//构造函数
template<class T>
Array<T>::Array(int sz){
    assert(sz>=0);//assert(int expression)先计算expression如果正确就继续,否则就abort
    size=sz;
    list=new T[size];//动态分配size个T类型的元素空间 
} 

//析构函数
template<class T>
Array<T>::~Array(){
    delete[]list;
} 

//复制构造函数
template<class T>
Array<T>::Array(const Array<T>&a){
    size=a.size;//从对象a取得数组大小,并赋值给当前的值 
    list=new T[size];//为对象申请内存并进行出错检查,动态分配
    for(int i=0;i<size;i++)
        list[i]=a.list[i]; 
} 

//重载运算符“=”,将对象rhs复制给本对象,实现四昂对象之间的整体赋值
template<class T>
Array<T> &Array<T>::operator=(const Array<T>&rhs){
    if(&rhs!=this){
        //如果本对象中数组大小与rhs不同,则删除数组原有内存,然后重新分配
        if(size!=rhs.size){ 
            delete[]list;//删除数组原有内存 
            size=rhs.size;//设置本对象的数组大小
            list=new T[size]; 
        }
        for(int i=0;i<size;i++)
            list[i]=rhs.list[i];
    }       
     return *this;
} 

//重载下标运算符,实现与普通数组一样通过下标访问元素,并且具有越界检查功能 
template<class T>
T &Array<T>::operator[](int n){
    assert(n>=0&&n<size);//检查是否越界
    return list[n];//返回下标为n的数组元素 
} 

template<class T>
const T&Array<T>::operator[](int n) const{
    assert(n>=0&&n<size);
    return list[n];
}

//重载指针转换运算符,将Array类的对象名转换为T类型的指针
//指向当前对象的私有数组
//因而可以像使用普通数组首地址一样使用Array类的对象名
template<class T>
Array<T>::operator T*(){
    return list;//返回当前对象中私有数组的首地址 
} 

template<class T>
Array<T>::operator const T*()const{
    return list;
}

//取当前数组的大小
template<class T>
int Array<T>::getSize()const{
    return size;
} 

//将数组大小修改为sz
template<class T>
void Array<T>::resize(int sz){
    assert(sz>=0);
    if(sz==size)
        return;//如果相等就不用修改了
    T *newList=new T[sz];
    int n=(sz<size)?sz:size;
    //将原有数组中前n个元素赋值到新数组中
    for(int i=0;i<n;i++){
        newList[i]=list[i];
    } 
    delete []list;
    list=newList;
    size=sz;
} 




int main(){
    Array<int>a(10);
    int count=0;
    
    int n;
    cout<<"Enter a value>=2 as upper limit for prime numbers:";
    cin>>n;
    
    for(int i=2;i<n;i++){
        //检查i是否能被比他小的质数整除
        bool isPrime=true;
        for(int j=0;j<count;j++)
            if(i%a[j]==0){
                isPrime=false;
                break;
            } 
        if(isPrime){
            //如果质数填满了,将其空间加倍
            if(count==a.getSize())
                a.resize(count*2);
            a[count++]=i; 
        }
    }
    for(int i=0;i<count;i++)
        cout<<setw(8)<<a[i];
    cout<<endl;
    return 0;
}

 


setw(int n);

作用:控制输出间隔
栗子:cout<<'s'<<setw(8)<<'a'<<endl;
则在屏幕显示s a//s和a之间由7个空格,setw()只对其后面紧跟的输出产生作用,如上例中,表示'a'共占8个位置,不足的用空格填充。若输入的内容超过setw()设置的长度,则按实际长度输出。
setw()默认填充的内容为空格,可以setfill()配合使用设置其他字符填充。
cout<<setfill('')<<setw(5)<<'a'<<endl;
则输出:
****a //4个
和字符a共占5个位置。
注意几点
①设置域宽的时候应该填入整数,设置填充字符的时候应该填入字符。
②我们可以对一个要输出的内容同时设置域宽和填充字符,但是设置好的属性仅对下一个输出的内容有效,之后的输出要再次设置。即cout <<setw(2) <<a <<b;语句中域宽设置仅对a有效,对b无效。
③setw和setfill被称为输出控制符,使用时需要在程序开头写上#include "iomanip.h",否则无法使用。


引用

int &a=5;&是引用,所以直接输出a的话就是5
int *p=a;直接输出p的话是a的地址

一、引用简介

引用就是某一变量(目标)的一个别名,对引用的操作与对变量直接操作完全一样。
  引用的声明方法:类型标识符 &引用名=目标变量名;
  【例1】:int a; int &ra=a; //定义引用ra,它是变量a的引用,即别名
  说明:
  (1)&在此不是求地址运算,而是起标识作用。
  (2)类型标识符是指目标变量的类型。
  (3)声明引用时,必须同时对其进行初始化。
  (4)引用声明完毕后,相当于目标变量名有两个名称,即该目标原名称和引用名,且不能再把该引用名作为其他变量名的别名。
   ra=1; 等价于 a=1;
  (5)声明一个引用,不是新定义了一个变量,它只表示该引用名是目标变量名的一个别名,它本身不是一种数据类型,因此引用本身不占存储单元,系统也不给引用分配存储单元。故:对引用求地址,就是对目标变量求地址。&ra与&a相等。
  (6)不能建立数组的引用。因为数组是一个由若干个元素所组成的集合,所以无法建立一个数组的别名。

二、引用应用

1、引用作为参数

引用的一个重要作用就是作为函数的参数。以前的C语言中函数参数传递是值传递,如果有大块数据作为参数传递的时候,采用的方案往往是指针,因为 这样可以避免将整块数据全部压栈,可以提高程序的效率。但是现在(C++中)又增加了一种同样有效率的选择(在某些特殊情况下又是必须的选择),就是引 用。
  【例2】:
   void swap(int &p1, int &p2) //此处函数的形参p1, p2都是引用
   { int p; p=p1; p1=p2; p2=p; }
  为在程序中调用该函数,则相应的主调函数的调用点处,直接以变量作为实参进行调用即可,而不需要实参变量有任何的特殊要求。如:对应上面定义的swap函数,相应的主调函数可写为:

main( )
{
 int a,b;
 cin>>a>>b; //输入a,b两变量的值
 swap(a,b); //直接以变量a和b作为实参调用swap函数
 cout<<a<< ' ' <<b; //输出结果
}

上述程序运行时,如果输入数据10 20并回车后,则输出结果为20 10。
  由【例2】可看出:
  (1)传递引用给函数与传递指针的效果是一样的。这时,被调函数的形参就成为原来主调函数中的实参变量或对象的一个别名来使用,所以在被调函数中对形参变量的操作就是对其相应的目标对象(在主调函数中)的操作。
  (2)使用引用传递函数的参数,在内存中并没有产生实参的副本,它是直接对实参操作;而使用一般变量传递函数的参数,当发生函数调用时,需要给 形参分配存储单元,形参变量是实参变量的副本;如果传递的是对象,还将调用拷贝构造函数。因此,当参数传递的数据较大时,用引用比用一般变量传递参数的效 率和所占空间都好。
  (3)使用指针作为函数的参数虽然也能达到与使用引用的效果,但是,在被调函数中同样要给形参分配存储单元,且需要重复使用"*指针变量名"的 形式进行运算,这很容易产生错误且程序的阅读性较差;另一方面,在主调函数的调用点处,必须用变量的地址作为实参。而引用更容易使用,更清晰。
  如果既要利用引用提高程序的效率,又要保护传递给函数的数据不在函数中被改变,就应使用常引用。

2、常引用

常引用声明方式:const 类型标识符 &引用名=目标变量名;
  用这种方式声明的引用,不能通过引用对目标变量的值进行修改,从而使引用的目标成为const,达到了引用的安全性。
  【例3】:
   int a ;
   const int &ra=a;
   ra=1; //错误
   a=1; //正确
  这不光是让代码更健壮,也有些其它方面的需要。
  【例4】:假设有如下函数声明:
   string foo( );
   void bar(string & s);
  那么下面的表达式将是非法的:
   bar(foo( ));
   bar("hello world");
  原因在于foo( )和"hello world"串都会产生一个临时对象,而在C++中,这些临时对象都是const类型的。因此上面的表达式就是试图将一个const类型的对象转换为非const类型,这是非法的。
  引用型参数应该在能被定义为const的情况下,尽量定义为const 。

3、引用作为返回值

要以引用返回函数值,则函数定义时要按以下格式:
   类型标识符 &函数名(形参列表及类型说明)
  {函数体}
  说明:
  (1)以引用返回函数值,定义函数时需要在函数名前加&
  (2)用引用返回一个函数值的最大好处是,在内存中不产生被返回值的副本。
  【例5】以下程序中定义了一个普通的函数fn1(它用返回值的方法返回函数值),另外一个函数fn2,它以引用的方法返回函数值。

#include <iostream.h>
float temp; //定义全局变量temp
float fn1(float r); //声明函数fn1
float &fn2(float r); //声明函数fn2
float fn1(float r) //定义函数fn1,它以返回值的方法返回函数值
{
 temp=(float)(r*r*3.14);
 return temp;
}
float &fn2(float r) //定义函数fn2,它以引用方式返回函数值
{
 temp=(float)(r*r*3.14);
 return temp;
}
void main() //主函数
{
 float a=fn1(10.0); //第1种情况,系统生成要返回值的副本(即临时变量)
 float &b=fn1(10.0); //第2种情况,可能会出错(不同 C++系统有不同规定)
 //不能从被调函数中返回一个临时变量或局部变量的引用
 float c=fn2(10.0); //第3种情况,系统不生成返回值的副本
 //可以从被调函数中返回一个全局变量的引用
 float &d=fn2(10.0); //第4种情况,系统不生成返回值的副本
 //可以从被调函数中返回一个全局变量的引用
 cout<<a<<c<<d;
}

引用作为返回值,必须遵守以下规则:
  (1)不能返回局部变量的引用。这条可以参照Effective C++[1]的Item 31。主要原因是局部变量会在函数返回后被销毁,因此被返回的引用就成为了"无所指"的引用,程序会进入未知状态。
  (2)不能返回函数内部new分配的内存的引用。这条可以参照Effective C++[1]的Item 31。虽然不存在局部变量的被动销毁问题,可对于这种情况(返回函数内部new分配内存的引用),又面临其它尴尬局面。例如,被函数返回的引用只是作为一 个临时变量出现,而没有被赋予一个实际的变量,那么这个引用所指向的空间(由new分配)就无法释放,造成memory leak。
  (3)可以返回类成员的引用,但最好是const。这条原则可以参照Effective C++[1]的Item 30。主要原因是当对象的属性是与某种业务规则(business rule)相关联的时候,其赋值常常与某些其它属性或者对象的状态有关,因此有必要将赋值操作封装在一个业务规则当中。如果其它对象可以获得该属性的非常 量引用(或指针),那么对该属性的单纯赋值就会破坏业务规则的完整性。
  (4)引用与一些操作符的重载:
  流操作符<<和>>,这两个操作符常常希望被连续使用,例如:cout << "hello" << endl; 因此这两个操作符的返回值应该是一个仍然支持这两个操作符的流引用。可选的其它方案包括:返回一个流对象和返回一个流对象指针。但是对于返回 一个流对象,程序必须重新(拷贝)构造一个新的流对象,也就是说,连续的两个<<操作符实际上是针对不同对象的!这无法让人接受。对于返回一 个流指针则不能连续使用<<操作符。因此,返回一个流对象引用是惟一选择。这个唯一选择很关键,它说明了引用的重要性以及无可替代性,也许这 就是C++语言中引入引用这个概念的原因吧。 赋值操作符=。这个操作符象流操作符一样,是可以连续使用的,例如:x = j = 10;或者(x=10)=100;赋值操作符的返回值必须是一个左值,以便可以被继续赋值。因此引用成了这个操作符的惟一返回值选择。
  【例6】 测试用返回引用的函数值作为赋值表达式的左值。

#include <iostream.h>
int &put(int n);
int vals[10];
int error=-1;
void main()
{
put(0)=10; //以put(0)函数值作为左值,等价于vals[0]=10;
put(9)=20; //以put(9)函数值作为左值,等价于vals[9]=20;
cout<<vals[0];
cout<<vals[9];
}
int &put(int n)
{
if (n>=0 && n<=9 ) return vals[n];
else { cout<<"subscript error"; return error; }
}

(5)在另外的一些操作符中,却千万不能返回引用:+-*/ 四则运算符。它们不能返回引用,Effective C++[1]的Item23详细的讨论了这个问题。主要原因是这四个操作符没有side effect,因此,它们必须构造一个对象作为返回值,可选的方案包括:返回一个对象、返回一个局部变量的引用,返回一个new分配的对象的引用、返回一 个静态对象引用。根据前面提到的引用作为返回值的三个规则,第2、3两个方案都被否决了。静态对象的引用又因为((a+b) == (c+d))会永远为true而导致错误。所以可选的只剩下返回一个对象了。

4、引用和多态

引用是除指针外另一个可以产生多态效果的手段。这意味着,一个基类的引用可以指向它的派生类实例。
  【例7】:
   class  A;
   class  B:public A{……};
   B  b;
   A  &Ref = b; // 用派生类对象初始化基类对象的引用
  Ref 只能用来访问派生类对象中从基类继承下来的成员,是基类引用指向派生类。如果A类中定义有虚函数,并且在B类中重写了这个虚函数,就可以通过Ref产生多态效果。
  三、引用总结
  (1)在引用的使用中,单纯给某个变量取个别名是毫无意义的,引用的目的主要用于在函数参数传递中,解决大块数据或对象的传递效率和空间不如意的问题。
  (2)用引用传递函数的参数,能保证参数传递中不产生副本,提高传递的效率,且通过const的使用,保证了引用传递的安全性。
  (3)引用与指针的区别是,指针通过某个指针变量指向一个对象后,对它所指向的变量间接操作。程序中使用指针,程序的可读性差;而引用本身就是目标变量的别名,对引用的操作就是对目标变量的操作。
  (4)使用引用的时机。流操作符<<和>>、赋值操作符=的返回值、拷贝构造函数的参数、赋值操作符=的参数、其它情况都推荐使用引用。


resize()函数

a.resize();
resize(),设置大小(size);
reserve(),设置容量(capacity);
size()是分配容器的内存大小,而capacity()只是设置容器容量大小,但并没有真正[分配内存]。
打个比方:正在建造的一辆公交车,车里面可以设置40个座椅(reserve(40);),这是它的容量,但并不是说它里面就有了40个座椅,只能说明这部车内部空间大小可以放得下40张座椅而已。而车里面安装了40个座椅(resize(40);),这个时候车里面才真正有了40个座椅,这些座椅就可以使用了

operator 两种用法

operator overloading 操作符重载
Array<T>&operator=(const Array<T>&rhs);
Array<T> operator+(const Array<T>&rhs);
Clock& operator++()//前置单目运算符
Clock operator++(int)//后置单目运算符
赋值(=)、下标([])、调用(())和成员访问箭头(->)运算符必须是成员(函数)。
返回值是引用:[],前置++/--,+/-=,=;

operator casting操作隐式转换
重载指针转换运算符
针对这个——> Array<int>a(10);
T &operator[](int i)

void read(int *p,int n){
    for(int i=0;i<n;i++)
        cin>>n; 
}
int main(){
    int a[10];
    read(a,10);
    return 0;
}

这里函数read的第一个形参是int指针,二数组名a是一个int型地址常量,类型恰好匹配。但如果希望在程序中像使用普通数组一样使用Array类的对象,将上述main函数修改如下:

int main(){
      Array<int>a(10);
      read(a,10);
      return 0;
}

总结

  • Array<T> &Array<T>::operator=(const Array<T>&rhs)返回值是引用还是函数本身是引用?
    返回值是引用,因为是对本身修改,不是返回副本
  • return list[n];为什么不是返回list?
    返回的就是下标的那个元素的引用呀,它的返回值是引用,所以也符合了[]的操作,list是数组的头元素的引用
  • Array和Array<T>的区别是什么?
    一个是用了模板定义的类呀
  • return *this; *this不是一个对象吗?this才是指针吧

 
 

题目

函数原型为 template <class T>
T avg_array(T* array, int count);
在main函数中,分别用double,float类进行测试;如果此函数模板对Point类型的数组(即此时T所对应的类型为Point)也能求平均(我们不妨把他叫做平均点,定义为横坐标和纵坐标的分别求平均,由此两个平均值组成的Point对象),
该如何编写该函数模板和Point类呢?(即要求该函数模板对double,float,Point类型都适用,提示:需要对Point类进行加法,除法等运算符重载)
这题我原本不是这样写的,我把x和y分开来了,但是看了Y的代码才发现这样才是简单的,另外他的代码出错的地方在于对象数组的初始化,他刚开始的数组里是没有point的。

#include<iostream>
using namespace std;
template<class T>
T avg_array(T *array,int count)
{
    T sum;
    for(int i=0;i<count;i++)
    {
        sum=sum+array[i];
    }
    return sum/count;
}
class point
{
    public:
        point(float x=0,float y=0):x(x),y(y){
            x=x;
            y=y;
        }
        friend point operator+(const point &c1,const point &c2)
        {
            return point(c1.x+c2.x,c1.y+c2.y);
        }
        friend point operator/(const point &c1, int count)
        {
            return point(c1.x/count,c1.y/count);
        }
        friend ostream & operator<<(ostream &out,const point &c)
        {
            out<<"("<<c.x<<","<<c.y<<")";
            return out;
        }
    private:
        float x,y;
};
int main()
{
    const int count=4;
    float a[count]={1.1,2.2,3.3,4.4};
    double b[count]={5.5,6.6,7.7,8.8};
    point c[count]={point(1,1),point(2,2),point(3,3),point(4,4)};



    cout<<"float类的平均值为:"<<avg_array(a,count)<<endl;
    cout<<"double类的平均值为:"<<avg_array(b,count)<<endl;
    point d=avg_array(c,count);
    cout<<"point类的平均值为:"<<d<<endl;
    return 0;
}

相关文章

  • ★07.关于类模板

    简述 类模板:是类类型的模板,如:vector。 模板类:类模板的实例化,如:vector 。 类模板的模板参数无...

  • 14/12

    成员模板:模板类中成员变量是模板类对象(1),模板类中函数是模板函数(2)

  • 14/15

    约束模板友元 模板类的外边定义一个模板函数,在模板类中将模板函数具体化为模板类的友元函数 非约束模板友元 模板类中...

  • 读书笔记:《学会写作》-模板写作、新媒体写作

    继续《学会写作》的阅读。 “模板写作”第一节作者给出了四类实用的模板:通知类模板、分析类模板、计划类模板和总结类模...

  • 模板方法模式

    模板抽象类 模板抽象类实现类 使用

  • 模板与泛型 —— using 定义模板别名

    一、类的成员函数模板 二、using 定义模板别名 一、类的成员函数模板 普通类和模板类,其成员函数都可以是模板函...

  • 10-C++远征之模板篇-学习笔记

    C++远征之模板篇 将会学到的内容: 模板函数 & 模板类 -> 标准模板类 友元函数 & 友元类 静态数据成员 ...

  • 慕课网-C++远征之模板篇(上)-学习笔记

    C++远征之模板篇 将会学到的内容: 模板函数 & 模板类 -> 标准模板类 友元函数 & 友元类 静态数据成员 ...

  • 类模板和模板类

    我是一个不喜欢看定义的人,我个人觉得知道怎么用就行了,这是一种糟糕的表现,可能是快节奏的的社会,让我做出的选择。从...

  • C++ STL vector

    vector是一个类模板,模板本身不是类或函数(类模板和函数模板),相反可以将模板看作编译器生成类或函数的一份说明...

网友评论

      本文标题:类模板

      本文链接:https://www.haomeiwen.com/subject/mxzgtctx.html