c++学习笔记(15.类模板)(二)

2014-11-24 08:28:31 · 作者: · 浏览: 1
); return 0; } 注意:这种类模板的局部特化是很容易产生二义性的,比如test 和 test 就会产生二义性,使得编译器不知道怎么办。而且这些二义性是没有优先级的,所以一定要小心使用,在程序设计上避免二义性,否则就会报错!

7.为什么需要特化,而不重新定义新类:

a.特化和重新定义新类看上去没有本质区别,但是如果定义新类,那么将变成一个类模板和一个新类,使用的时候需要考虑究竟是用类模板还是用新类 b.而特化可以统一的方式使用类模板和特化类,编译器自动优先选择特化类

8.非类型模板参数:

a. 函数模板和类模板的模板参数可以是普通数值,如 template 示例代码:
#include 
     
      

using namespace std;

template 
      
        void fun() { T array[N] = {0}; for(int i = 0; i
       
         class test { public: T a; void fun() { T array[N] = {0}; for(int i = 0; i
        
         (); //函数模板 的非类型模板参数 test
         
           t1; //类模板 的非类型模板参数 t1.fun(); return 0; } 
         
        
       
      
     
b. c++编译器会对非类型模板参数进行如下的限制: 第一,变量不能作为非类型模板参数 第二,浮点型和类对象不能作为非类型模板参数 第三,全局指针不能作为非类型模板参数 总之,非类型模板参数只允许int型常量,包括#define定义的常量,const定义的常量,enum枚举常量,普通常量!
c. 一个经典的面试问题:如何才能使程序最快的计算出1加到n? 第一个想法:写一个函数通过高斯公式计算1加到n,时间复杂度为O(n) 第二个想法:使用宏,省去了函数调用过程中的时间 第三个想法:使用类模板,非类型模板参数与特化(利用递归的思想),省去高斯公式在代码执行期间计算的时间(使其在编译期间完成计算),代码如下:
#include 
     
      

using namespace std;

template 
      
        // 使用非类型模板参数的 类模板 class sum { public: static const int value = sum < N-1 > :: value + N; }; template <> //递归的出口 使用类模板的特化 class sum<1> { public: static const int value = 1; }; int main() { cout << (sum <100> :: value) <
       
         我们分析下上面的代码: 第一,上面的代码使用递归思想(这里的递归是不断的通过sum类模板创建具体N数值的sum类),实际上是根据sum类模板,创建出来了N个sum类(当然不是都叫sum),每一个类中又有一个类的静态成员变量value。 第二,其实上面的递归过程,是在编译期间,创建出N个sum类,计算出每个sum类中的value值,可见sum<100> :: value 这个过程在程序执行期间,仅仅是从内存中获得一个变量的时间,连计算的时间都没(计算是在编译期间完成的),所以它的时间最短! 第三,虽然这个方法是用时最短的,但是过于极端,所浪费空间是巨大的!
        
第四,编译器的推导过程是在编译阶段完成的。因此,编译器的推导必须依赖于特化类(特化类是递归的出口),否则推导过程无法结束,编译器会报错!

9.类模板在工程中的问题:

a.在实际工程中内存操作是bug的重要来源 b.c++将堆内存交给程序员自由使用,因此常出现下面三个bug: 第一,未及时释放内存,造成内存泄露 第二,重复释放同一个段内存,行为未知 第三,内存越界,操作了不属于自己的内存 c. 解决内存越界的问题,内存越界常发生于数组中,可使用数组类来避免这个问题。工程中,在非特殊情况下,要求开发者使用预先编写的数组类对象代替c语言中的原生数组。数组类的优势在于,在类中有一个length成员变量,根据数组类对象就可以知道数组的具体长度!原生数组是不可以的!但是原生数组的内存是在栈区的,数组类的内存是在堆区的! d. 解决内存泄漏和内存多次释放的问题,这个问题常发生于指针的使用过程中,可以使用智能指针来避免这个问题。在工程中,要求开发者使用预先编写的智能指针类对象代替c语言中的原生指针! e. 智能指针: \
示例代码: samrtpoint.hpp:
#ifndef _SMARTPOINT_HPP_
#define _SMARTPOINT_HPP_

#include "smartpoint.h"

template 
         
          
smartpoint
          
           ::smartpoint() { s_point = NULL; } template 
           
             smartpoint
            
             ::smartpoint(const T* point) //这里的const是防止初始化的值被改变 其实意义不大 { s_point = const_cast
             
              (point); } template 
              
                smartpoint
               
                ::~smartpoint() { delete s_point; } template 
                
                  T& smartpoint
                 
                  ::operator*() { return *s_point; } template 
                  
                    T* smartpoint
                   
                    ::operator->() { return s_point; } #endif 
                   
                  
                 
                
               
              
             
            
           
          
         

smartpoint.h:
#ifndef _SMARTPOINT_H_
#define _SMARTPOINT_H_

template 
         
          
class smartpoint
{
protected:
	T* s_point;
public:
	smartpoint();//为那些不需要进行初始化的对象提供的构造函数  
	smartpoint(const T* point); //为那些需要进行初始化的对象提供的构造函数 
	~smartpoint(); //析构函数  帮助释放内存  防止内存泄漏和重复释放  
	T& operator*(); //两个很奇葩的操作符重载 
	T* operator->();
};

#endif

         

main.cpp:
#include 
         
          
#include "smartpoint.hpp"

using namespace std;
class test
{
public:
	void fun()
	{
		cout << "hello fun" <
          
            s_pi = new int(100); cout << *s_pi << endl; smartpoint
           
             s_pt = new test; s_pt->fun(); //s_pt.operator->() ->fun(); *s_pi = 500; cout << *s_pi << endl; return 0; } 
           
          
         
注意: 第一,因为智能指针将内存的释放处理交给了析构函数,其实就是交给了编译器,这样就避免了内存忘记释放(内存泄漏)和内存重复释放的问题! 第二,这个程序里面有两个奇葩的操作符重载,一个是->操作符的重载,另一个是*操作符的重载。对于+加 -减 *乘 这个三个运算符来说,如果操作符重载的函数没有参数,且函数是类的成员函数。使用操作符的时候,应该是 操作符加对象,即*s_pi。对象变成了右操作数,不知道为什么?对于->操作符的重载就更加奇怪了,当->操作符重载的函数没有参数,且函数时类的成员函数。使用操作符的时候,->符号起到了两次作用!即s_pt->fun()与 s_pt.operator->() ->fun()等效!!!原因不知道???