C++基础学习 更新ing....

目录

匿名空间

 匿名空间的定义

命名空间的跨文件使用 

 C语言中分配动态内存空间(堆空间的方式)

C++中用new分配堆空间   delete释放

C++中利用new分配二维数组

总结new 和malloc的区别

 c++ 中的引用

常量引用

引用作为函数的参数

引用作为函数的返回值

引用的注意事项:

c++ 中的函数重载

c++ 函数重载的原理

函数重载注意事项

c++ 中的类

类的三大特性 (封装,继承,多态)

c++ 类中的构造函数

析构函数

类中的空间大小分配与结构体一样

拷贝构造函数 (重点,难点)

类外定义类中的成员函数

类的继承

继承的语法

类继承成员属性的访问权限

继承中的构造函数与析构函数

类中的隐藏问题

类中的多继承

类中多级继承

菱形继承

虚函数

c++ 中的覆盖

多态

动态多态的实现条件:

虚析构 

纯虚函数 与 抽象类

c++ 中的友元

友元函数

友元类

运算符重载

 运算符重载的两种方式

不同运算符重载的方式

c++ 中的模板

什么是模板

模板类

 模板函数与模板类的区别

模板类作为函数的参数传递

 模板类的成员函数类外编写

模板类的继承



匿名空间

作用:限制 全局变量只能在当前文件中使用

            提示:功能与静态全局变量类似

  静态局部变量:1.只能初始化一次

                           2.会改变静态变量的生命周期,存放在数据段中

静态全局变量:  只能在当前文件中使用该全局变量

 匿名空间的定义

namespace   //不写空间名称他就是一个匿名空间
{
    int a=100;          //填写空间中的成员,变量,函数,类
    float b=3.14;
}

命名空间的跨文件使用 

1.把命名空间定义出来

namespace TX

{

  int a=100;

2.在使用该控件中的文件进行生命

namespace TX

{

    extern int a;    // 声明该空间中的变量a 实在外部定义的


 C语言中分配动态内存空间(堆空间的方式)

void *malloc(size_t size); //分配大小 为size 的堆空间 

void free(void *ptr);//通过堆空间的首地 址释放该空间

void *calloc(size_t nmemb, size_t size);//分配 nmemb * size 大小的堆空间

void *realloc(void *ptr, size_t size);//把ptr 的堆空间改变大小为size

void *reallocarray(void *ptr, size_t nmemb, size_t size); //把ptr 的堆空间改变大小size*nmemb

C++中用new分配堆空间   delete释放

        数据类型 *变量名 =new 数据类型; //分配一块内存

例如:     int *p=new int ;           /  / 自己计算空间的大小,自己进行类型的转换

                int *p2=new int[100] ;   //分配100块 int类型的堆空间 

                int *q=new int (100);  // 支持分配空间时候对其进行初始化

                int *q=new int (10){1,2,3,7};// 分配度空间进行初始化,其他空间默认为0;

释放对空间:   delete 变量名

例如 :         delete p;

                delete q;

C++中利用new分配二维数组

int main(int argc, char const *argv[])
{    
  int  (*p6)[10]=new int[10][10];
  //p6二维数组赋值
     int x=0;int y=0;
     cout<<"*p6:\n";
    for(x=0;x<10;x++)
    {
        for(y=0;y<10;y++)
        {
            p6[x][y]=x+y;
        }
    }
    
     for(x=0;x<10;x++)
    {
        for(y=0;y<10;y++)
        {
            cout<<p6[x][y]<<"\t";
        }
        cout<<endl;
    }
    cout<<endl;
delete []p6;
}

总结new 和malloc的区别

1.new可以在自动计算数据类型的大小和类型转换  malloc只能手动进行

2.new可以再分配空间的时候初始化                       malloc 不可以

3.再分配类的堆空间时,使用 new 会调用<<类中的构造函数>>    mallo不可以


 c++ 中的引用

   引用:给已知的变量取一个别名,内存不会再为该引用分配新的空间,节省内存空间!!

 引用的定义语法:数据类型 &引用名= 引用的变量名;

例如 :                      int a=100;  //对变量a 取一个别名

                                  int &q=a;    //a q 是完全等价的!!

引用的好处:        这样传递参数的过程中就不会分配新的变量空间!! 节省空间!!

引用的使用场合:一般都是用来作为参数传递,让函数直接访问数据的 本身,节省临时空间的产生!!

常量引用

作用: 1.防止引用名,去修改原来数据的值。     3. 特殊写法:数据类型  &&引用名;

            2.对数据常量进行引用

             const  数据类型 &引用名=引用的对象; 

  例如:1.    int  a=100;                              |         2.    const int &q=100;

                   const int &p=a;                        |         3.   int  &&a=100;   //常量应用  

引用作为函数的参数

           引用作为函数的参数,用户不需要修改该数据时,那么我们就可以传递常量引用!

int  add(const int &a,const int &b)
{
    return a+b;  //并未修改ab 的数据,只是访问 
}

int main()
{
        int a=10,b=20;  
        cout << add(a,b)  << endl;  //可以传递变量  

        cout << add(100,200)  << endl; //也可以传递常量
}

引用作为函数的返回值

 注意!!!!

 1.引用作为返回值时一般返回: 静态变量,全局变量,堆空间,(传递的参数引用)...

 2.当一个引用作为返回时,该函数可以作为左值

 3.当引用作为返回值时,必须要保证函数结束后该空间还是存在的!!

引用的注意事项:

1.引用必须要初始化  (因为引用就是取别名,取别名的对象都没有,哪里来的别名?)
2.引用一个常量时,需要使用const  常量引用 或者   && 右值引用 
3.引用的类型必须要匹配    

4.引用被初始化后就无法再次修改。


c++ 中的函数重载

1.用相同的函数名定义不同的功能称为函数重载。
2.重载的函数根据参数的个数和类型进行区分。
3.不能根据返回类型进行区分:

c++ 函数重载的原理

 1.c++ 编译器在编译的时候会 《自动检查》用户编写的函数《参数列表》, 根据参数列表的      不同对函数名进行别名化,来产生多个名称不一样的函数.

2.根据用户传入的 《参数》 来自动推导类型,去确定要执行的函数。 

int pf(int a)            / /1.编译器自动别名化  -->  pf_int(int a); 
{
    cout << "打印一个整形数据"  << a << endl; 
    return 0;
}

int pf(char a)            //1.编译器自动别名化 -->  pf_char(char a) 
{
    cout << "打印一个字符数据" << a << endl; 
    return 0;
}

int main()
{
    //一个pf 函数拥有两种不同的功能      
pf(100);                //2.根据传入的参数不同,自动推导调用的函数  100 ->int->pf_int(int a); 
pf('A');               //2.根据传入的参数不同,自动推导调用的函数'A'->char->pf_char(char a) ; 
}
                        //都是c++ 编译器自动去完成的!!不需要用户干预!! 

函数重载注意事项

1.函数重载时函数名 必须一样 
2.函数重载的依据  <参数个数>  <参数类型>  不一样! 
3.返回值类型不能作为重载的依据!! 
4.调用重载的函数时要防止 《歧义》 的产生    

5.默认参数的填写顺序必须是从右到左,调用函数的时候参数传递的顺序是从左到右


c++ 中的类

类的三大特性 (封装,继承,多态)

封装: 1.把功能函数接口封装到类里面

           2.把数据成员封装到类里面

继承:  儿子从爸爸里面继承一些东西过来。 (子类继承父类的函数接口或数据)

            优点:子类直接继承父类的接口函数,不需要重新开发这些接口,提高代码的复用性,有                         利于软件版本的升级。

多态: 一个对象作用于不同的事物,所得到的结果(功能)不一样!!

            优点: 提高代码的复用性

面向对象的编写优点:

1.代码容易修改与维护 (因为每个对象都是独立的,需要修改对象的内容即可)

2.提高代码的复用性 (继承,多态,函数重载,默认参数 )->ps 少写一些代码

3.能够更好的设计复杂的程序 (因为很多的一些第三方开源库,都是利用c++ 去设计的,假设我们想要用这些开发库,那么必须掌握c++语言。 )一般类的定义方式:

class 类名 
{
   public:   //共有成员,外界可以访问  
    
   protected:  //保护成员,只有儿子可以访问
    
   private: //私有成员,只有当前类内部可以访问
        
};

//定义一个类的例子: 
class base
{
    public:
        int a; //外界可以访问,因为是共有成员 
    protected: 
         int b;  //子类可以访问,因为保护成员 
    private: 
         int c;  //只有类的内部可以访问 ,因为是私有成员
};

c++ 类中的构造函数

构造函数: 1.函数名与类名相同,
                   2.函数没有返回值, 
                   3.函数不需要用户调用,在创建对象的时候自动调用

class  类名
{
     public:
  	类名()   //构造函数
    {
        
    }   
}
例子: 
    class base
    {
        public: 
         base()
         {
             cout << "调用构造函数" << endl;
         }
    }

注意: 1.构造函数必须要写在公共区,因为在创建对象的时候会自动该函数,假设不是功能区的                   函数,则无法掉,导致对象的创建失败 。

           2.假设用户没有写任何的构造函数,则系统会自动生成一个无参构造函数。

this 指针作用:用来区分类的内部 与外部成员。 每当用户创建一个类时,这个类就会自动生成一                          个 this 指针,指向当前类的首地址!! 

   base(int a,int b,int c)
     {
         //this->a  当前类-》a 成员
          this->a=a;
          this->b=b;
          this->c=c;
     }
//不使用  this 指针那么  a ,b ,c 变量的名字要重新修改!! 

析构函数

析构函数:1.函数名与类名相同在函数名前面添加~, 
                  2.析构函数没有返回值,《没有参数》
                  3.当对象销毁的时候系统自动调用(析构函数可以释放成员空间)

析构函数的作用: 用于释放构造函数中初始化的数据成员。

 注意:析构函数是不可以重载的,因为重载的依据是参数!

 

类中的空间大小分配与结构体一样

 1.在设计类的数据成员时,应该把数据类型,从小到大排序的去定义,这样定义出来的类,      所占用的内存空间最小

 2.类中的空间大小,只于类中的数据成员有关,与类中的成员函数无关。

 3. 空类的大小为1

拷贝构造函数 (重点,难点)

系统自动生成的浅拷贝方法:浅拷贝 ----> 直接赋值

#include  <iostream> 
using namespace std; 
class  base
{
public: 
   //设计一个构造函数 
   base(int a,int b):a(a),b(b){}
void show()
{
     cout << a << endl;
     cout << b << endl;
}
private:
int a; 
int b;
};
int main()
{
         base  a(10,20);
         a.show();
         //通过一个对象去初始化另外一个人对象 
         base b=a;  //系统会自动生成一个浅拷贝构造函数,把a对象的数据赋值给b对象
         b.show(); 
}

重写系统的拷贝构造函数 

当在构造函数中传递的参数是类的引用时,那么这个构造函数就是重写后的拷贝构造函数。

当用户重写拷贝构造函数后,系统就不会自动生成一个浅拷贝构造方法 。

class  base
{
    public: 
    base(base &a)
    {
        cout <<  "重写后的拷贝构造函数" << endl; 
    }

 深拷贝  

为什么需要深拷贝:拷贝后的堆内存空间共享。

 

实现深拷贝:
class  base
{
public:
base(const char *str)
{
        //分配p所指向的堆空间 
        p  =  new  char[1024]; 
        //把str数据赋值到堆空间中 
        strcpy(p,str);  
}

//重写拷贝构造函数,实现深拷贝!!!!!
base(base &a)
{
    cout << "调用重写后的拷贝构造函数,实现深拷贝" << endl; 
     //分配p所指向的堆空间  
     p= new  char[1024];  
     //把a空间中的数据拷贝过来  
     strcpy(p,a.p);
}

 void  show()
 {
     cout << "堆空间的地址" << (void *)p << "内容" << p << endl; 
 }
char *p;
};

类外定义类中的成员函数

函数的返回值   类名::函数的名称(参数列表)
{
    
}
-------------------------------------------
例子: 
class base
{
   public: 
    void show(); //声明 
}; 

//类外定义show 接口
void   base::show()
{
    cout << "show base" << endl; 
}

类的继承

作用: 1.提高代码的复用性

            2.有利于软件版本的升级

继承的语法

    class  类名: 继承方式  基类名(父类名)
{
      //填写派生类的成员 (子类的成员) 

类继承成员属性的访问权限

1.假设想要在派生类中,直接访问基类的数据成员,那么基类的数据成员只能 设置为共有权      限或保护权限。 

2.假设想要在派生类中,访问基类的私有成员,只能在基类中设计接口,让派生类间接访         问。  

继承中的构造函数与析构函数

 基类和派生类的构造函数都会执行: 
   构造函数的执行顺序: 先调用基类的构造函数,再调用派生类的构造函数  
基类和派生类的析构函数都会执行:
   析构函数的执行顺序: 先调派生类的析构函数,再调用基类的析构函数. 
总结: 在类的继承中,一定要处理好这些 基类 与派生类的构造函数 与析构函数。让派生类可以调用到 自己的构造 与 基类的构造。否则无法创建对象! 

类中的隐藏问题

1.在继承中,如果子类与父类出现同名函数,则子类会把父类的功能函数给隐藏掉。

2.假设想要在子类中是用父类被隐藏掉的接口, 是用 域操作符 指定是用父类接口

a.base::show();

3.派生类中,只要编写了一个与父类同名的函数就会把父类的方法给隐藏了《不支持 重载》

就是不支持跨类重载。

4.通过父类引用 派生类 也可以调用父类的方法。 base &=new_base //通过父类引用子类

class  base
{
 public: 
   void show()
   {
         cout <<  "show base" << endl; 
   }
};
//基类 和 派生类出现了同名函数成员。
class  new_base :public base
{
 public:  
  void  show(int a,int b)  //对父类的show 方法进行重载 
  {    
         cout << a << endl; 
         cout << b << endl; 
  }
};
int main()
{
     new_base  a; 
     a.show(); //调用父类的 show 方法 ,无效的调用。因为父类的方法已经被隐藏了
     a.show(10,20); //调用自己的 show 方法 
     //利用域操作符 ,声明是用父类的方法  
     a.base::show(); 

}

类中的多继承

         一个派生类可以继承多个基类, 获取多个基类的功能接口和数据成员,提高代码的复用性。

 class 派生类 :继承方式 基类 ,继承方式 基类2  ........

}
例子: 
class base : public  base1 ,public  base2      


 构造函数的执行顺序: base1 -> base2 -> base 
 析构函数的执行顺序:  base -> base2 -> base1 
 
//提示: 哪一个先继承,那么就先执行他的构造 ,从左 -》 右

类中多级继承

class 基类名  
class 派生类 :继承方式 基类名 
class 派派生类 : 继承方式  派生类 

例子: 
class  base 
class  base_a :public base 
class  base_b :public base_a

构造函数的执行顺序:  base  -> base_a  ->  base_b 
析构函数的执行顺序:  base_b -> base_a ->   base  

菱形继承

注意:菱形继承会出现一个二义性问题,导致派生类无法调用基类的所有功能函数!!  

1.利用 域操作符,指定使用哪一个基类中的函数方法 
      a.base_a::show(); 
      a.base_b::show(); 
2.利用 基类引用派生类,再调用出现二义性的方法  
       base_a &q=a; 
       base_b &q=a; 

3.在派生类中重写出现二义性的方法,把之前的隐藏掉。  
    
4.最强的处理方法: 利用虚继承,让base 的空间存放在虚表中,不构造两次!! 
base: 
base_a :virtual  public  base     //-> base_a 虚继承base -》 系统就会把 base 放到虚表中
base_b :virtual  public  base    //->base_b 虚继承base -> base_b 发现虚表已经存在base了就直接使用不再分配。 

虚函数

语法: 
virtual 函数返回类型 函数名(参数表) {函数体};  
例子:定义虚函数 
virtual  void func(int ,int )
{
       
}
//定义了一个虚函数,系统就会产生一个虚表。  

 结论:

1.当类中含有一个虚函数时,系统就会自动分配一个虚表指针(*vptr),指向系统的虚表
2.用户定义的所有虚函数都在该虚表中。 
3.虚表的地址,永远都在对象空间的最前面。
4.重点《子类会把父类的虚表也继承下来,子类与父类公用一个虚表》(实现动态多态的条件!!)

c++ 中的覆盖

回顾隐藏: 子类与父类拥有一个同名函数,子类就会把父类的功能接口给隐藏掉。假设要是用父类接口,通过对父类进行引用,再调用他的接口。

 覆盖当基类拥有一个虚函数,并在派生类中,对该虚函数进行了重写。 这种就是覆  盖!!

覆盖的条件:

1.基类必须要有虚函数 
2.派生类必须要重写基类的虚函数 
3.通过基类 的指针  或 引用,指向派生类,并调用重写的方法。     
只有满足上述三个条件才会出现这种覆盖现象。  


多态

什么是多态??

一个对象,作用于不同的事物,所以得到的结果不一样。 (得到状态不一样)

《 一个函数可以实现多种不同的功能!!!》

作用: 提高代码的复用性。


静态多态 : 在程序 《编译》的时候,已经确定将要执行的状态。 -》函数重载

动态多态:在程序 《运行》的时候,才能确定执行的状态。 根据用户传递不同的对象,执行不同的状态。

动态多态的实现条件

1.基类要有虚函数
2.继承
3.派生类重写基类的虚函数 
4.把派生类的对象,赋值给基类的  指针  或 引用  ,通过基类调用被重写的接口.  

#include<iostream>
using namespace std;
class person 
{
  public :
         virtual void zhuangbei()
         {
            cout<<"初始化装备, ";
         }
         virtual void juese()
         {
              cout<<"初始化角色"<<endl;
         }
       virtual  ~person()
         {
               cout<<"person 析构"<<endl;
         }

};
class JC:public person
{
    public:
     virtual void juese()
         {
              cout<<"警察:";
         }
    void   zhuangbei()
    {
        cout<<"手枪"<<endl;
    }   
       virtual ~JC()
         {
               cout<<"JC 析构"<<endl;
         }
} ;
class yisheng:public person
{
    public:
     virtual void juese()
         {
              cout<<"医生:";
         }
    void   zhuangbei()
    {
        cout<<"针"<<endl;
    }   
   virtual ~yisheng()
         {
               cout<<"yisheng 析构"<<endl;
         }

} ;
void set(person *p)
{
        p->zhuangbei();
    p->juese();

    delete p;
}

int main(int argc, char const *argv[])
{
    set(+);
    set(new JC);
    set(new yisheng);
    
    return 0;
}

虚析构 

因为通过基类指针去释放派生类的堆空间,系统是无法调用派生类的析构函数的。

所以就有可能造成派生类构造函数中初始化的一些数据成员未被释放。造成资源的浪费!!

virtual ~析构函数{}  

 virtual ~new_base(){} 

总结: 析构函数不管是否应用到多态技术,最好都把他设置为虚析构。 万无一失!  

纯虚函数 与 抽象类

 纯虚函数的定义: 
virtual 函数返回类型 函数名(参数表) =0;
例子: 
virtual  void  func()=0;     //纯虚函数  
抽象类:   如果一个类中的有纯虚函数那么这个类就是抽象类
抽象类的特点: 抽象类不能实例化 (定义对象)

作用:设计代码的预留框架接口,让派生类去实现。 

c++ 中的友元

作用: 用于访问类中的所有数据成员。

缺点: 破坏类的封装性,所以万得已,不要随便是用友元。

友元函数

声明友元函数的语法:

friend 函数的返回值类型  函数名(参数列表)  
例子: 
friend  void  show()  //该函数就可以访问声明他的类中的所有成员 
{
}   

1.当一个函数被声明为该类的友元函数时,该函数就不属于类中的成员函数!!

2.友元函数必须要在类内,声明!

3.友元函数可以在类中的任意地方声明, 与成员权限无关。

友元类

作用: 当一个类为 另外一个类的友类,则该类可以访问对方的一切数据成员。

class test{
public: 
  //单纯声明一个接口
 void set_base();
private: 
int c;
//声明 base  是 test 的友元 
friend  class base;
};

class  base{
public: 
//访问 test 中的私有成员
void set_test(){
    test  a; 
    a.c = 100;
}
private: 
int c;
//声明test 是base 的友元 
friend class  test;
};

//重点!!重点!!在base 定义后 才实现  set_base 接口 
void test::set_base()
{
      base  a; 
      a.c = 200;
}

运算符重载

作用: 给已知的运算符赋予新的功能

以往所学习的运算符只能进行基本的数据类型运算符,无法实现 两个字符串的相加 , 两个类的相加,两个结构体的相加。 这时候我们就可以利用 c++ 新增的运算符重载的方法赋予运算符新的功能实现这些数据的运算。

 返回类型说明符 operator 运算符符号(<参数表>)
例如:重载两个 base 类相加的运算符   
    base operator+(base a)
   { 
   }

可重载运算符:

1,那些运算能重载
双目运算符  (+,-,*,/, %)     operator+ 
关系运算符 (==, !=, <,  >, <=, >=)
逻辑运算符 (||,   &&,  !)
单目运算符 (*, &, ++, --)
位运算符   (|,  &,  ~, ^,  <<, >>)
赋值运算符 (=, +=, -=,   .....)
空间申请运算符 (new , delete)
其他运算符 ((),  ->,  [])
    
2,那些运算符不能重载
.(成员访问运算符)
.*(成员指针访问运算符)
::(域运算符)
sizeof(数据类型长度运算符)
?:(条件运算符, 三目运算符)

 运算符重载的两种方式

 类内重载方式:  
    base  operator+(参数列表)  
调用规律: 
   base c = a+b;  -> a.operator+(b)   等价于这种调用方式。 
   特点: a是调用者,  b是参数。 因为是在类的内部重载所以有 this 指针 
       
类外重载方式:  
       base  operator+(base a,base b) 
       调用规律: 
       base c=a+b;  ->   operator+(a,b)  等价于这种调用方式。单纯的函数调用
        特点: a和 b 都是 operator+ 函数里面的参数,所以不可以使用 this 

不同运算符重载的方式

1.关系运算符

class base
{
public: 
base(int a):a(a){}
private: 
int a;
friend bool   operator>(base a,base b);
};

//类外重载
bool   operator>(base a,base b)
{
    if(a.a > b.a)
    {
        return true;
    }else{
        return  false;
    }
}

int main()
{

   base a(100); 
   base b(20); 

   if(a > b)
   {
       cout << "a > b" << endl;
   }else{
       cout << "a<b"  << endl;
   }
}

2.逻辑运算符

class base
{
public: 
base(int a):a(a){}

bool  operator&&(base b)
{
    if(this->a && b.a)
    {
        return true;
    }else
    {
        return false;
    } 
}

private: 
int a;
};

3.单目运算符

class base
{
public:
int a;
base(int a):a(a){}

//++a  先自加,再用值
base operator++()  
{
    cout << "调用++a"  << endl;
    this->a++;  //先自加 
    return *this; //再返回值 
}
//a++  先用值,再自加
base  operator++(int)
{
    cout << "调用a++"  << endl;
    base tmp=*this; //保存原来的值 
    this->a++;

    return tmp;  //先返回值 
}
};
int main()
{
     base a(0);  
     base b=a++;  //先用值,再自加

        cout << b.a << endl; 
        cout << a.a << endl; 
}

4.输出运算符的重载

class  base
{
public: 
base(int a):a(a){}
private:
int a;
friend ostream &operator<<(ostream &out,base a);
};
//类外重载方式
ostream &operator<<(ostream &out,base a)
{
    cout << a.a;

    return out;
}
int main()
{
  
      base a(10086); 
      base b(10010); 
      cout  << a << b << endl;
}

5.输入运算符的重载

class  base{
public:  
base(){}
base(int a):a(a){}
int a;
};
//类外重载输入运算符 
istream &  operator>>(istream &in,base &a)
{
     cin >> a.a;
     return in;
}

int main()
{

     base  a;
     base  b;

     cin  >> a >> b; 

     cout << a.a << endl;
     cout << b.a << endl;

}

c++ 中的模板

什么是模板

模板就是做好的一个样式,用户直接去套用就可以快速实现功能。

模板是不能够直接使用,必须要实例化才能用。

 函数模板: 拥有一个通用类型的函数。

通用类型的定义语法:

template<typename 通用类型> 或 template<class 通用类型>
    
//例子:声明一个带有通用类型的函数  
template<class T>   
void  show(T a)
{
}  //声明了一个模板函数,带有一个通用类型T   
T  的类型,系统会自动根据用户传入的参数来推导出他的类型。 

template <class T>
void jiaohuan(T &a,T &b)
{
    T c = a; 
      a = b; 
      b = c;
}

int main()
{

   int a=10,b=20; 
   jiaohuan(a,b);
   cout << a << ":"  << b << endl;

   float a1=3.14,b1=4.44; 
   jiaohuan(a1,b1);
   cout << a1 << ":"  << b1 << endl;

   string  a2="hello",b2 ="world"; 
    jiaohuan(a2,b2);
   cout << a2 << ":"  << b2 << endl;
}

1.模板函数与普通函数区别

*普通函数调用,可以发生自动类型转换

自动类型转换 从精度 底的数据 往 精度高的数据进行转换

char -> short -> int -> float -> double

*模板函数调用,没有自动类型转换,只会发生自动类型推导

 2.模板函数与普通函数的调用规则

1.当模板函数 与 普通函数 实现的功能相同时,优先调用普通函数。

2.假设用户不想调用普通函数,那么怎么做?? 利用 <> 指定是用模板。

3.当模板函数比普通函数,更好匹配参数时,那么编译器,就会优先调用模板函数。

(c++的编译器是很智能的!!)

4.模板函数也支持重载。

 3.模板函数的局限性

对于用户自定义的数据类型,模板函数是无法对数据进行操作的。

如果模板函数出现了局限性,那么就特殊情况特殊处理!!

#include <iostream>

using namespace std; 

class  base
{
public:  
     base(int a):a(a){}
private:
int a;
//friend  bool   big(base a,base b);
friend  bool   operator>(base a,base b);
};

//重载 base 的比较运算符 
bool operator>(base a,base b)
{
   if(a.a > b.a)
    {
        return true;
    }else
    {
        return false;
    }
}

//设计一个比较两个数据大小的模板 
template <class T>
bool   big(T a,T b)
{
    if(a > b)
    {
        return true;
    }else
    {
        return false;
    }
}

/*
//特殊数据进行特殊处理 
bool   big(base a,base b) 
{

    if(a.a > b.a)
    {
        return true;
    }else
    {
        return false;
    }
}
*/

int main()
{

    base a=100,b=20;
    if(big(a,b))
    {
        cout  << "a > b"  << endl;
    }else
    {
        cout << " a <= b"  << endl;
    }
}

模板类

    当一个类中拥有一个通用的数据类型,那么这个类就是一个模板类,模板类在没实例化前,不能定义对象。

 template <class 通用类型>
class  类名 
{
    通用类型 变量名
}; 
-------------------------------------------
例子:  
template <class T>
class  base
{
    T a;
}; //-> 这就是一个模板类

记住模板类!!没有自动类型推导功能!必须要用户手动实例化!!

 模板函数与模板类的区别

1.模板类没有 自动类型推导功能,模板函数有

2.模板类可以使用默认类型

 模板的默认类型使用:

template <class T=int>
class  base
{
public: 
    base(T a):a(a){}
    void show()
    {
        cout << a << endl;
    }
private:
T a;
};

int main()
{
    base<>  a(3.1415126); //不写类型,则使用上面的默认类型 ,就算添加了默认类型 <> 也是需要填写了
                            //区分该类是一个模板类 
    a.show();
    base<double>  b(3.1415126);  //使用用户指定的模板参数 
    b.show();
    
}

模板类作为函数的参数传递

      注意: 当一个模板类,作为函数的参数时,无法直接通过类名作为函数的参数传递!!

  解决方法:

1.在函数的参数列表中,指定模板的类型。   
      void show_base(base<int> a)  
    
2.继续把类 参数类型模板化  
template <class T>
void show_base1(base<T> a)
    
3.直接把该函数设计为一个 纯模板函数  
   template <class T>
void show_base2(T a)  

#include <iostream>
using  namespace std;

//这是一个模板类 
template <class T>
class  base{
public: 
base(T a):a(a){}
T a; 
};

class  base1
{
public: 
base1(int a):a(a){}
int a; 
};


//1.指定模板类的参数列表,进行传递
void show_base(base<int> a)
{
    cout <<   a.a << endl;
}

//2.继续把类型模板化 
template <class T>
void show_base1(base<T> a)    //模板函数-》 自动类型推导功能
{
    cout <<   a.a << endl;
}

//3.把该函数设计为模板函数
template <class T>
void show_base2(T a) 
{
     cout << a.a << endl;
}

int main()
{

     base<int>  a(10086); 
      //调用函数 
      show_base(a); 
      show_base1(a);
      show_base2(a);
}

 模板类的成员函数类外编写

#include <iostream>

using  namespace std; 

//定义一个模板类 
template <class T>
class base
{
   public:
    //单纯的声明接口
    base(T a); 
   ~base(); 
   void show(); 

private: 
T a;
};

//类外实现这些接口 
template <class T>
base<T>::base(T a)
{
    this->a = a;
}

template <class T>
base<T>::~base()
{
   cout << " base析构函数" << endl;
}

template <class T>
void base<T>::show()
{
    cout << this->a << endl;
}

int main()
{

   base<int>  a(10086); 
   a.show();
}

模板类的继承

       模板类不能直接被继承

解决方法:

        1. 指定基类的模板类型

        2.继续把派生类模板化

猜你喜欢

转载自blog.csdn.net/m0_52467164/article/details/127607261