C++归纳复习类篇

C++归纳复习类篇

  • 前言:在19年1月,我花了一个月时间去学习C++,已经过了两年了,我也工作了两年了,由于工作中不常使用C++,因此花时间重新复习一些C++的特性,也好为人生中第一次跳槽做准备。

感谢这位大佬的记录,我和这位大佬学的教程应该是同一个:https://www.cnblogs.com/lifexy/

目录

1.class中的访问权限

1.1 public

一个类的public成员变量成员函数,可以通过类的函数、子类的函数、友元函数、类对象直接访问。

1.2 private

一个类的private成员变量,只能被该类的方法和友元函数访问,子类函数不能访问。

1.3 protected

一个类的protected成员变量成员函数,可以被该类的成员函数访问,不能被类对象所访问,但可以被子类的函数和友元函数访问。

2.class和struct

在C++中,class和struct只有默认访问权限不一样,其它都是一样的,struct的默认权限是public,而class的默认权限是private。

struct:

//默认权限为public
struct r1chie
{
  int i;
  void test(void);
};

int main()
{
    struct r1chie t1;
    t1.i = 10;
    
    return 0;
}

class:

//默认权限为private
class r1chie
{
    int i ;
    void test(int x)
    {
        i = x;
    }
    
};

int main()
{
    r1chie t2;
    t2.test(10);
    
    return 0;
}

3.构造函数

  • 创建局部对象后,对象存放在栈中,对象中的成员变量的初始值是随机数。

  • 创建全局对象后,对象放在静态变量区,对象中的成员变量的初始值为0。

  • 只用mallc给对象分配内存,对象存放在堆中,对象中的成员变量的初始值为随机数。

  • 使用new给对象分配内存,对象存放在堆中,对象中的成员变量的初始值为随机数。

构造函数的功能之一,可以用来初始化成员变量。

3.1 构造函数的特点

  • 构造函数与类的名字相同
  • 构造函数可以带参数,但没有返回值
  • 构造函数在对象创建时,被调用。
  • 构造函数可以被重载
class r1chie
{
 private:
    int i;
  public: 
    r1chie()
    {
        i = 10;
        printf("hello this is r1chie\n");
    }
    int geti(void)
    {
        return i;
    }
    
};

int main(void)
{
    r1chie t1;
    printf("%d\n",t1.geti())
    return 0;
}

//执行结果:
hello this is r1chie
10

3.2 构造函数的重载

类同样也有C++重载的特性

构造函数可以有多个不同参数:

class r1chie
{
 public:
    r1chie(){}
    r1chie(int i){}
    r1chie(int i , int j){}
    
};

int main()
{
    r1chie t1;       //r1chie()
    r1chie t2(2);    //r1chie(int i)
    r1chie t3(3,4);  //r1chie(int i , int j)
    r1chie t4 = 10;  //r1chie(int i)
    
    return 0;
}

3.3 特殊的构造函数

3.3.1 无参数构造函数

当类中没有定义构造函数时,编译器默认提供一个函数体为空的无参构造函数。

如果已经定义了一个构造函数,那么编译器就不会去生成默认的构造函数。

class r1chie{
  public:
    	r1chie(int i){}
};

int main()
{
    r1chie t1; //报错,原因是已经定义了r1chie(int i) , 编译器就不会帮我们定义r1chie();
    return 0;
}


3.3.2 拷贝构造函数

当类中没有定义拷贝构造函数时,编译器会默认提供一个拷贝构造函数,进行对类的成员变量进行简单的拷贝。

class r1chie
{
    int i;
    public:
    	r1chie();
//    	  r1chie(const r1chie& r)
//        {
//            i = r.i;
//        }
    
}

int main()
{
    r1chie r1;
    r1chie r1 = r2;  //使用编译器生成的拷贝函数,编译器生成的就是我注释的r1chie(const r1chie& r);
    //如果已经定义了r1chie(const r1chie& r),编译器就不会帮我们生成
}

3.3.2.1 浅拷贝

通俗一点,就是简单粗暴的进行赋值操作,不做任何处理,在一些普通的成员变量可以这么使用。

class r1chie
{
    int i;
    char a;
    int *p;
  public:
    r1chie()
    {
        i = 10;
        a  = 'a';
        p = new int(1);
    }
    
    void free()
    {
        delete p;
    }
    
};

int main()
{
    r1chie r1;
    r1chie r2 = r1;
    
    r1.free();
    r2.free(); //报错,原因是拷贝构造函数里面只是简单的把 r1.p 赋值给 r2.p,
    		   //r1已经释放过了p,r2再去释放肯定会报错。
}
3.3.2.2 深拷贝

为了避免上一种情况的发生,引出深拷贝,就是在一些成员变量使用系统资源的时候,做一些合适的处理:

class r1chie
{
  int i;
  int *p;
  public:
    r1chie()
    {
      	i = 10;
        p = new int(10);
    }
    r1chie (const r1chie& r)
    {
        i = r.i;
        p = new int(*(r.p)); 
        //不是直接把r.p的地址直接赋值,而且重新创建一块内存
    }
    void free()
    {
        delete p;
    }
    
    
};

int main()
{
    r1chie r1;
    r1chie r2 = r1;
    
    r1.free();
    r2.free(); //成功
    return 0;
}


3.4 构造函数的对成员变量的初始化

当类的成员变量使用const修饰时,如果在成员函数(包括构造函数)中初始化const成员变量的值,那么在编译的时候一定会报错。
class r1chie
{
	int const a;
    
   public:
   		r1chie() //即使屏蔽了这个函数,也会编译报错,原因是该类的成员变量a没有被初始化。
        {
            a = 10; //编译报错
        }  
    
};

因此,引出显示初始化,既然有显示初始化,就肯定有隐式初始化。其实隐式初始化就是在构造函数中对变量的初始化。

3.4.1 隐式初始化

class r1chie
{
  int i;
    
  public:
    //这种方式就是隐式初始化
    r1chie(int x)
    {
         i  = x;
    }
    
};

3.4.2 显示初始化

也叫做构造函数初始化列表,使用这种方法,就可以对const成员变量进行初始化。

初始化列表位于构造函数名的右侧,以一个冒号开始,再接着初始化的变量,有多个成员变量要被初始化,就是用逗号隔开。

class r1chie
{
  int a;    //第一个声明,所以第一个被初始化
  int b;    //第二个声明
  const int c; //第三个声明
    
 public:
    r1chie():b(1),a(2),c(3)  //初始化列表
    {
        
    }
    
};

那么他们的初始化顺序并不是列表的顺序,而是声明的顺序,a > b > c。

3.5 对象的构造函数执行顺序

  • 对于局部对象来说,程序执行到对象的定义语句时,进行构造。
  • 对于全局对象来说,构造顺序是不确定的,所以要减少全局对象直接的耦合。
  • 对于new分配的对象来说,和局部对象的一样的。

3.6 子类对象的构造执行顺序

class father
{
	public:
    	father()
        {
            cout << "I am father" << endl;
        }
};

class Friend
{
    int i;
    public:
    	Friend(int x)
        {
            i = x;
            cout << "I am friend" << endl;
        }
};

class child : public father
{
    int a;
  	Friend f;
 public:
    child() : a(10) , f(10)
    {
        cout << "I am child" << endl;
    }
};

int main()
{
    child c1;
    
    return 0;
}

输出结果:
I am father
I am friend
I am child

一句话总结:先父亲,再朋友,后自己。

4.析构函数

析构函数,在类对象生命周期结束时会被调用。

4.1 析构函数的特点

  • 析构函数没有返回值和参数
  • 析构函数不可以被重载
  • 析构函数不能被显示调用,只能被系统自动调用
  • 析构函数应该设置为类的公有成员
  • 如果不定义析构函数,编译器默认生成一个空的析构函数
class r1chie()
{
    int i;
  public:
    	r1chie(int x)
        {
            i = x;
            cout << "hello " << i << endl;
        }
    	~r1chie()
        {
            cout << "bye" << i<< endl;
        }
};

int main()
{
    r1chie r1(1);
    r1chie *p = new r1chie(2);
    
    delete p;
}

执行结果:
hello1
hello2
bye2
bye1

4.2 析构函数通常的作用

对象成员变量中,如果有使用到系统资源,那么可以在析构函数执行的使用进行释放,这样就不用担心系统资源使用完毕后没有被释放。
class r1chie
{
    int *p;
    
  public:
    r1chie(int x)
    {
        p = new int(x);
    }
    
    ~r1chie()
    {
        delete p;
    }
    
};

int main ()
{
    r1chie *r1 = new r1chie(10);
    
    delete r1; //会执行析构函数~r1chie()去释放p
}

4.3 子类对象的析构执行顺序

与构造函数完全相反:先自己,再朋友,后父亲

5.this指针

  • this指针是所有成员函数的隐含参数
  • this指针只能在成员函数中使用
  • this指针不能再静态函数中使用
class r1chie{
  int x;
  int y;
    
 public:
    r1chie(int x, int y)
    {
        this->x = x;
        this->y = y;
    }
    
};

6.const 常对象

6.1 const修饰对象

  • 被const修饰的对象只能调用const修饰的成员(变量和函数)
  • const对象只能调用const成员函数,而非const对象可以访问const成员函数
  • const对象不能修改成员变量
class r1chie{
    
   public:
   		int i;
    
    	r1chie(int x)
        {
            i = x;
        }
    
    	void test() const
        {
            
        }
    
    	void fun()
        {
            
        }
    
};

int main()
{
    const r1chie r1(0);
    r1chie r2(0);
    
    r1.test(); //正确
    r1.fun(); //错误,const对象只能调用const成员函数。
    r1.i = 0; //错误,const对象成员变量不允许改变。
    
    
    r2.test(); //正确
    r2.fun();  //正确
    r2.i = 100; //正确
    
}

6.2 const修饰成员函数

  • 定义const成员函数时,在函数声明的最右侧带上const关键字

  • const成员函数中不能直接修改成员变量的值

  • const成员变量不能被修改

class r1chie{
    int a;
    const int b;
  	public:
    r1chie(): a(1), b(2)
    {
        
    }
    
    int geta()
    {
        return a;
    }
    
    int getb() const  //在函数声明的最右侧带上const关键字
    {
        	b = 10; //错误,const成员变量不能被修改
    	    a = 10; //错误,const成员函数中不能直接修改成员变量的值
        	return b;
    }
    
};

int main()
{
    const r1chie r1;
    r1chie r2;
    
    cout << r1.geta() << endl;  //错误,const对象只能调用const成员函数
    cout << r1.getb() << endl; 
    
    cout << r2.geta() << endl;  
    cout << r2.getb() << endl; 
    
    return 0;
}

7.static 静态成员变量、函数

  • 所有对象共享类的静态成员变量
  • 静态成员变量的声明周期不随着对象的结束而结束,生命周期类似于全局变量,那么静态成员变量存储的位置在全局数据区
  • 可以通过类名或对象名直接访问public静态成员变量

7.1 static静态成员变量

class r1chie
{
    public:
    	static int i;    
    	//如果使用const修饰static成员变量,可以在类内部进行初始化
    	const static int a = 0; 
    
};

int r1chie::i = 1; //在类外初始化,如果不初始化默认为0
int main()
{
 
    cout << r1chie::i << endl;
    
    return 0;
}

7.2 static静态成员函数

  • 静态成员函数属于整个类所有
  • 静态成员函数没有隐含this指针,不能直接访问非静态成员变量
  • 可以通过类名或对象名访问public静态成员函数
class r1chie
{
    static int cnt;
    public:
    	static int func(){
            return cnt;
        }
    	//或者在类外实现
    	//static int func();
    
};

//在类外实现
//int r1chie::func()
//{
    
//}

int r1chie::cnt = 0; //在类外初始化

int main()
{
    //不需要创建对象,就可以直接通过类名调用static成员函数
    cout << r1chie::func() << endl;
    
}

8.二阶构造

一般我们在构造函数里面初始化一些成员变量,有一些成员变量可能用到了系统资源,获取系统资源通常是要申请的,如果我们在构造函数里面申请,那么如果申请失败,也没办法返回错误,因为构造函数不能返回值,那么我们就可以使用二阶构造的方法,来防止申请失败没有提示。

思路就是把初始化分为两部分,一部分初始化是不需要系统资源的,一部分是需要系统资源的:
class r1chie
{
    private:
    r1chie()
    {
        //里面初始化非系统资源
    }
    bool construct()
    {
        //里面初始化需要系统资源的
        return true;
    }
    
    public:
    
    //提供二阶构造的接口
    static r1chie* newObj()
    {
        r1chie *ret = new r1chie();
        
        if( ! (ret && ret->construct())  )
        {
            delete ret;
            return NULL;
        }
            
        return ret;    
    }
    
};

int main()
{
    r1chie *p = r1chie::newObj();
    if(p == NULL)
    {
    	cout << "ERROR" << endl;    
    }
    
    return 0;
    
}

9.友元friend

  • 友元函数能够访问类的private属性的变量
  • 一个类可以是另一个类的友元
class r1chie
{
 private:
    int a;
 public:
    //友元函数
    friend int geta(const r1chie& r); 
    
    //一个类是另一个类的友元
    friend class r1chie_friend;
};


class r1chie_friend
{
    public:
    	int get_friend_i(const r1chie& r)
        {
           return r.a;
        }
};


int geta(const r1chie& r)
{

	return r.a;
}

int main()
{
	r1chie r;
    r1chie_friend rf;
    
    //友元函数可以访问private属性的成员变量
	cout << geta(r) << endl; 
    
    //一个类是另一个类的友元,那么该类的对象可以访问另一个类的对象的private属性的成员变量
    cout << rf.get_friend_i(r) << endl; 
    
    return 0;
}

缺点:破坏了面向对象的封装性

10.操作符的重载

  • 允许全局操作符和成员操作符的重载

  • 操作符的重载不改变操作符本身的优先级

  • 操作符的重载参数必须要和之前的一致

  • 操作符重载的参数一般设为const class_name &类型(若只是const class_name,会产生临时对象)

  • C++中,有些操作符必须有对象的支持,所以只能为成员操作符重载函数,被成为一元操作符。

    • 赋值 =、下标 [ ] 、调用 () 、成员访问箭头 ->

10.1 一元成员操作符和二元全局或成员操作符

一元成员操作符重载函数:

r1chie r1 = r2; // 实际上就是 r1chie r1.operator = (t2)
  • 有些操作符既可以当作成员操作符重载函数,也可以当作全局操作符重载函数,由于有多个参数,被称为二元操作符。

    • 加法+、与&&、或|| 、逗号 ,

    二元成员操作符重载函数:

r1chie r1 = r2 + r3; //实际上 r1 = r2.operator + (r3)


例子:

```c++
class r1chie
  {
    int x;
    int y;
    
      public:
      	r1chie(int x , int y )
          {
              this->x = x;
              this->y = y;
              
          }
      
      	int getx()
          {
              return x;
          }
      	
      	int gety()
          {
              return y;
          }
      
      	r1chie operator + (const r1chie& r)
          {
              r1chie ret(0,0);
              ret.x = this->x + r.x;
              ret.y = this->y + r.y;
              
              return ret;
          }
      
  };
  
  int main()
  {
      r1chie r1(10,10);
      r1chie r2(20,20);
      
      //当r1+r2返回一个临时对象时,即r3 = 临时对象,那么r3就会使用浅拷贝把x、y赋值
      r1chie r3 = r1 + r2;
      
      cout << r3.getx() << endl;
      cout << r3.gety() << endl;
      
      return 0;
  }
  
  输出结果:
  30
  30

二元全局操作符重载函数:

r1chie r1 = r2 + r3; // 实际上  r1 = operatot + (r2,r3)

例子:

class r1chie
{
  int x;
  int y;
    
    public:
    	r1chie(int x , int y)
        {
            this->x = x;
            this->y = y;
        }
    	
    	int getx()
        {
            return x;
        }
    
    	int gety()
        {
            return y;
        }
    
    	friend r1chie operator +(const r1chie& r1,const r1chie& r2);
};

r1chie operator +(const r1chie& r1,const r1chie& r2)
{
    r1chie ret(0,0);
    ret.x = r1.x + r2.x;
    ret.y = r1.y + r2.y;
    return ret;
}

int main()
{
    r1chie r1(10,10);
    r1chie r2(20,20);
    //当r1+r2返回一个临时对象时,即r3 = 临时对象,那么r3就会使用浅拷贝把x、y赋值
    r1chie r3 = r1 + r2;
    
    cout << r3.getx() << endl;
    cout << r3.gety() << endl;
    
    return 0;
}


输出结果:
30
30

10.2 多个重载的操作符重载函数

由于操作符重载函数是带参数的,因此可以通过不同的参数类型来对函数进行重载
class r1chie
{
    double x;
    double y;
    
    public:
    	r1chie operator +(const Test& t);
    	r1chie operator +(int i);
    	r1chie operator +(double d);    
};

10.3 操作符重载的练习

复数类的实现,要实现复数的加减乘除、比较、赋值。

class Complex{
  private:
    double a;
    double b;
  public:
    Complex(int a=0, int b = 0)
    {
		this->a = a;
		this->b = b;
	}
    
    Complex operator +(const Complex& c)
    {
        Complex ret;
        ret.a = this->a + c.a;
        ret.b = this->b + c.b;
        
        return ret;
    }
    
    Complex operator -(const Complex& c)
    {
        Complex ret;
        ret.a = this->a - c.a;
        ret.b = this->b - c.b;
        
        return ret;
    }
    
    Complex operator *(const Complex& c)
    {
       Complex ret;
       ret.a = ((this->a)* c.a - (this->b) * c.b );
       ret.b = (c.a * (this->b) +(this->a)* c.b );    
       return ret;     
    }
    
    Complex operator /(const Complex& c)
    {
       Complex ret;
       ret.a = ((this->a)* c.a + (this->b)* c.b)/(c.a * c.a + c.b * c.b);
       ret.b = ((this->b)* c.a - (this->a)* c.b)/(c.a * c.a + c.b * c.b);    
       return ret;   
    }
    
    bool operator == (const Complex& c)
    {
        if((a == c.a) && (b == c.b))
            return true;
        else
            return false;
    }
    
    bool operator != (const Complex& c)
    {
        if((a != c.a) && (b != c.b))
            return true;
        else
            return false;
    }
    
    
    //this指针指向的是这个对象,因此返回的是this指向的对象,而不是返回this这个指针
    Complex& operator = (const Complex& c)
    {
		if(this != &c)
        {
            a = c.a;
            b = c.b;
        }
        return *this;
    }
    
    
    
    double geta()
    {
        return a;
    }
    
    double getb()
    {
        return b;
    }
        
    
};

int main()
{
    Complex c1(10,10);
	Complex c2(20,20);
	Complex c3 = c1 + c2;  //加法
	Complex c4 = c1;       //赋值
	
	cout << c3.geta() << endl;
	cout << c3.getb() << endl;
    
	if(c4 == c1) //比较
		cout << "c4 == c1" <<endl;
	
	if(c4 != c2)  //比较
		cout << "c4 != c2" << endl; 
	
	
 	return 0;   
}

10.4 操作符 ( ) 的重载

函数对象

  • 函数对象是指该对象具备函数的行为
  • 函数对象,是通过调用 ( ) 调用操作符声明得到的
  • () 调用操作符可以定义不同参数的多个重载函数
  • () 调用操作符只能通过类成员函数重载
  • 函数对象用于在工程中取代函数指针
class r1chie
{
  public:
    void operator ()(void)
    {
        cout << "hello world" << endl;
    }
    
};

int main()
{
    r1chie r1;
    r1();
    
    return 0;
}


10.5 重载操作符 && || 逗号,

“&&”和“||”是拥有短路功能的,

  • 比如:a = (0 && b) ,首先就会判断第一个操作数,发现为0,那么就不会去判断b的值
  • 比如:a = (100 || b),首先会判断第一个操作数,发现为不为0,那么就不会去判断b的值

那么我们可以重载这两个操作符,使其失去短路功能:

class r1chie
{
    int value;
	
   public:
    r1chie(int x)
    {
        value = x;
    }
    
    
    int getvalue(void) const  //const类型
    {
        return value;
    }
    
};

//在func中的参数类型是const,所以 getvalue() 也是const类型
bool operator && (const r1chie& r1,const r1chie& r2)
{
    return r1.getvalue() && r2.getvalue();
}

r1chie func(r1chie r)
{
  cout << "func()" << r.getvalue() << endl;
  return r;
}

int main()
{
    r1chie r1(0);
    r1chie r2(1);
    
    int b = (func(r1) && func(r2));
    cout << "b:" << b << endl;
    
     return 0;
}

输出结果:
func()1
func()0
b:0

​ 结果是先执行了func(r2),再执行func(r1),(func(r1) && func(r2))实际上是:bool operator && (r1,r2),初始化的顺序是先r2,再r1,也就是从右往左初始化。

逗号操作符

  • 逗号表达式前N-1子表达式不需要返回值
  • 逗号表达式从左往右计算,且最终的值等于最后一个表达式
int i = 5, b = 4;
int a = (i++,i++,b+4,b=5,i++);// 这句代码结果 a = 7,b = 5,i = 8
(i,b,a) = 10; //这段代码的结果:a = 10,其它不变
int a[3][3] = {
  (1,2,3),
  (4,5,6),
  (7,8,9)
};

//结果:
//a[0][0] = 3;
//a[0][1] = 6;
//a[0][2] = 9;

重载逗号操作符注意事项:

  • 重载后的逗号操作符不会具备从左往右计算的功能
  • 尽量使用全局函数来重载
  • 逗号重载函数的参数必须有一个是class类的类型(目的是让编译器知道,逗号是用户重载)
  • 逗号重载函数的返回值必须是类型的引用(因为可能要对返回值进行运算)
  • 逗号重载函数的返回值必须是最后一个参数的值
class r1chie
{
  int value;
  
  public:
  	r1chie(int x = 0)
    {
        value = x;
    }
    
    int getvalue()
    {
        return value;
    }
    
    r1chie operator +(int i)
    {
        r1chie ret;
        ret.value = this->value + i;     
        cout << ret.value << endl;       
        return ret;
        
    }
    
};

r1chie& operator ,(const r1chie& r1, const r1chie& r2)
{
    return const_cast<r1chie&>(r2);
}

int main()
{
    r1chie t1(0);
    r1chie t2(5);
    
    r1chie t3 = (t1+1,t2+1);
    cout << t3.getvalue() << endl;
    
    return 0;
    
}

输出结果:
6
1
6    

​ 符合预期, (t1+1,t2+1)实际上 r1chie& operator , (t1+1,t2+1),上面分析重载的&&操作符号,可以知道参数初始化的顺序是从右往左的,所以先执行t2+1,再执行t1+1,t3的值依旧等于最后一个

10.6 重载操作符++

  • ++操作符分为前置++和后置++
  • ++操作符可以进行全局函数或成员函数重载
  • 重载前置++操作符不需要参数
  • 重载后置++需要一个int类型的占位参数
  • 前置++操作符的返回值为*this
  • 后置++操作符的返回值为临时对象
class r1chie
{
    int value;
    
  public:
    r1chie(int x = 0)
    {
        value = x;
    }
    
    int getvalue()
    {
        return value;
    }
    
    //++a,先++,再返回值
    r1chie& operator()
    {
        ++value;
        return *this;
    }
    
    //a++,先返回值,再++
    r1chie operator ++(int)
    {
        r1chi ret(value);
        ++value;
        return ret;
    }
        
};

10.7 重载类型转换函数

  • 类型转换函数用于将类对象转换为其它类型
  • 方法是通过operator关键字重载其它类型,返回类型不需要
class r1chie
{
  int value;
    
    public:
     r1chie(int i = 0)
     {
         value = i;
     }
    
    //重载类型转换,转换为int
    operator int()
    {
        return value;
    }
    
};

int main()
{
    r1chie r1(10);
    int i = r1;
    
    cout << i << endl;
}

输出结果:
10   
 

类与类之间的转换:

class r1chie2;

class r1chie1
{
    public:
    	r1chie1()
        {}
    
    	explicit r1chie1(r1chie2& r)
        {
            
        }
    
};

class r1chie2
{
    int value; 
    public:
        r1chie2(int i = 0)
    	{
        	value = i;
    	}
    
    	//把r1chie2 转换为 r1chie1
    	operator r1chie1()
        {
            r1chie1 ret;
            return ret;
        }
    
};

int main()
{
    r1chie2 r2(100);
    
    //如果r1chie1(r1chie2& r)不加上explicit,
    //导致编译器不知道这段代码到底是类型转换,还是初始化对象
    //加上了explicit就只能按照r1chie1(r1chie2& r)格式,才会被认为是初始化对象
    r1chie1 r1= r2; //类转换
    r1chie1 r3(r2); //初始化对象
    return 0;
}

11.C++标准库

11.1 重载操作符 <<


using namespace std;

int main()
{
    
    cout << "hello" << endl;
}

cout相当于控制台输出(console out),通过操作符 << 将每个字符打印出来,endl相当于'\n'

代码实现cout:

class r1chie
{
    public:
    	r1chie& operator << (const char* str)
        {
            printf("%s",str);
            return *this;
        }
    
    	r1chie operator <<(char c)
        {
            printf("%c",c);
			return *this;
        }
    
};

const char endl = '\n';

r1chie cout;

int main()
{
    
    cout << "hello world!" << endl;
	return 0;
}

11.2 字符串string类

C++标准库中,有string类,

头文件 :#include<string>,string功能比较强大

  • 支持字符串连接
  • 支持字符串大小比较
  • 支持子串查找和提取
  • 支持字符串的插入和替换
  • 可以使用数组方式访问,因为string类重载了 []操作符。
int main()
{
	string a;
	string b = "123456";
	
	for(int i= 0; i < 10; i++)
		a[i] = 0x56 + i;    //使用 [] 进行赋值,长度并不会改变,只是改变了a里面的内容
	
    cout << "a:"<< a << endl;	
	cout << a.length() << endl;
	
	cout << "b:"<<b << endl;	
	cout << b.length() << endl;
	
	return 0;
}

输出结果:
a:
0
b:123456
6

11.3 字符串与数字的转换

用到两个类

istringstream   //字符串输入流
ostringstream   //字符串输出流

头文件:

#include <sstream>

把字符串转换为数字:


istringstream iss("123.4");
double num;

if(iss >> num)
{
    cout << num << endl;
}

把数字转换为字符串

ostringstream oss;
double num = 123.4;

oss << num;
string r1chie = oss.str();
cout << r1chie << endl;


11.4 实现一个string类和重载操作符[]

class string 
{
  private:
    char *str;
    int len;
  public:
    string(const char *p = 0) //不初始化就默认为0
    {
        if(p != NULL)
        {
            len = strlen(p);
            str = new char[len];
            strcpy(str,p);
        }
        else
        {
            len = 0;
            str = new char[1];
            str[0] = "\0";
            
        }
    }
    
    char &operator [](int i)
    {
        return str[i];
    }
    
    int length()
    {
        return len;
    }
    
};

int main()
{
    string a = "r1chie";
    for(int i = 0; i < a.length(); i++)
    cout <<a[i] << endl;
    
    return 0;
}

12.智能指针

12.1 = 赋值操作符重载

  • 编译器为每个类默认重载了 = 赋值操作符
  • 默认的 = 赋值操作符就是浅拷贝
  • 默认的赋值操作符和默认的拷贝构造函数有相同存在的意义
class r1chie
{
    int *p;
    
    public:
    	
    	r1chie& operator = (const r1chie& r)
        {
            if(*this != r)
            {
                delete p;
                p = new int(*r.p);
            }
  			return *this;          
        }
};

12.2 智能指针

​ 在大型的C程序中,有大量使用malloc申请内存,如果不及时释放,那么一个设备允许了好几个月,很快就会报错。

因此C++中引入智能指针的概念

  • 智能指针是一个将指针封装在类里,通过对象管理指针
  • 在构造函数运行时,通过对象将指针传递进来,指针可以是缺省值
  • 然后“ -> ”, " * " ," = " 等操作符进行重载,让这个对象拥有指针的特性
  • 最后在析构函数中释放类里的指针
  • 智能指针只能指向堆空间,因为结束时会使用delete去释放
  • 一片空间只能由一个智能指针管理,避免多次释放
  • -> 和 * 都是一元操作符,所以不能带参数
class point
{
  int *p;
    
  public:
    point(int* p = 0)
    {
          this->p = p;      
    }
     
    //r1chie->value ,等价于 r1chie.operator ->() -> value,等价于 (*(r1chie.operator->())).value  
    int *operator ->()
    {
        return p;
    }
    
    int& operator * ()
    {
        return *p;
    }
    
    
    point& operator = (const point& x)
    {
    	if(this != &x)
        {
            delete this->p;
            this->p = x.p;
            const_cast<point&>(x).p = NULL;
        }
        
        return *this;
        
    }
	
	bool operator == (const point& x)
	{
		return (p == x.p);
	}
      
    
    ~point()
    {
		cout << "~point()" << endl;
        delete p;
    }
        
   
    
};

int main()
{
    point p = new int(10);
    point a;
	
	a = p;
	
	if(a == p)
	cout << "a == p" << endl;
	
    return 0;
    
}

12.3 STL中的智能指针

  • 生命周期结束时,自动销毁指向的内存空间
  • 不能指向堆数组(因为析构函数中删除指针的是delete,而不是delete [])
  • auto_ptr的构造函数为explicit类型,只能显示初始化
#include <memory> //头文件

auto_ptr<int> ap1(new int(1)); //使用类模板

int *p = new int(1);
autp_ptr<int> ap2(p);

//auto_ptr<int> ap3 = new int(2); //报错,隐式初始化

  • 可以使用get()成员函数来获取指针地址:
auto_ptr<int> ap1(new int(1));
cout << ap1.get() << endl; //打印指针地址
  • 一个智能指针只能指向一片堆空间内存(多个智能指针指向一个堆空间,那么析构的时候会出问题)
  • 当auto_ptr被拷贝或赋值后,那么原本的指针就会被初始化为0
auto_ptr<int> ap1(new int(1));
autp_ptr<int> ap2(new int(2));

ap1 = ap2;

cout << ap2.get() << endl;

例子:

class r1chie
{
    int x;
  public:
    r1chie(int i)
    {
        x = i;
        cout << "r1chie()" << x << endl;
    }
    ~r1chie()
    {
        cout << "~r1chie()" << x << endl;
    }       
};

void func()
{
    auto_ptr<r1chie> ap1(new r1chie(1));
    auto_ptr<r1chie> ap2(new r1chie(2));
    
    cout << "ap1 = ap2" << endl;
    ap1 = ap2;
    
    cout << "ap1 = " << ap1.get() << endl;
    cout << "ap2 = " << ap2.get() << endl;
    
}

int main()
{
    cout << "start fun()" << endl;
    func();  
    cout << "end fun()" <<endl;
}

输出结果:
start fun()
r1chie()1
r1chie()2
ap1 = ap2
~r1chie()1
ap1 = 0x1ae1ea0
ap2 = 0
~r1chie()2
end fun()

12.4 QT中的智能指针

  • 当其指向的对象被销毁时,本身会自动赋值为NULL
  • 缺点在于,改模板类析构时,不会自动销毁所指向的对象
QObject *obj = new Object;
QPointer<QObject> pObj(obj);

delete obj;

Q_ASSERT(pObj.isNull());

13.隐式转换explicit关键字

  • 当两个类型不同的变量进行计算时,按两个类型的大小决定。
    • char <- short <- int <- unsigned int <- long <- unsigned long <- float <- double
char a = -20;
unsigned int b = 10;
cout << a+b << endl;

结果:
4294967286   

原因是a+b的结果被转为了unsigned int类型,这就是隐式转换,带来的危害很大。

在使用构造函数的时候,也会碰到类似的问题:

class r1chie
{
    public:
    	r1chie(unsigned int i)
        {
            cout << i << endl;
        }
    
};

int main()
{
    r1chie r = -1;
    
    return 0;
}

输出结果:
4294967295

这也是隐式转换带来的问题。

为了解决这种隐式转换带来的问题,引入关键字explicit

class r1chie
{
    public:
    	explicit r1chie(unsigned int i)
        {
            cout << i << endl;
        }
    
};

int main()
{
    //此时就会发现编译过不了,原因是添加了explicit关键字,就必须进行显示转换。
    //r1chie r = -1;
    
    //编译可以通过,这是显示强制转换,打印的结果依然是:4294967294
    r1chie r = (static_cast<r1chie>(-2))
    
    return 0;
}

14.继承与组合

只有类才有继承的概念,组合是比继承更加简单,所以编程时,优先考虑组合能不能解决问题。

14.1组合

  • 将其它类的对象作为当前类的成员使用
class r1chie_1
{
    
};

class r1chie_2
{
    r1chie_1 r1;
};

14.2继承

  • 子类具有父类的属性和行为
  • 父类也叫基类,子类也叫派生类
  • 父类可以被多个子类继承,子类也可以继承多个父类(叫做多继承,不建议使用多继承,java是不允许的)
  • 子类是一个特殊的父类
  • 子类可以在子类内部访问父类protected属性的成员变量和函数
class father
{
    protected:
    int value;
    
    void small_call()
    {
      cout << "samll call "<<endl;  
    }
    
    public:
    father()
    {
        value = 10;
    }
    
    void call()
    {
        cout << "call" << endl;
    }
};

class child : public father
{
    public:
		void user_father_small_call()
		{
			small_call();
		}
	
    	int getfather_value()
        {
            return value;
        }
};

int main()
{
    father f1;
    child c1;
     

    int i = c1.getfather_value();//子类可以访问父类protected属性的变量
	cout << "i:" << i << endl;
    c1.user_father_small_call(); //子类可以访问父类protected属性的函数
		
    father f2 = c1; //子类可以初始化父类
    f1 = c1;        //子类可以复制给父类
    
    
    f1.call();
    c1.call();
}

输出结果:
i:10
small_call
call
call

14.3继承方式

有三种继承方式:

  • public继承:指父类的成员访问级别,在子类中保持不变。
  • protected继承:指父类的成员访问级别,在子类中,原public变为protected,原protected不变,原private不变。
  • private继承:指父类的成员访问级别,在子类中,原public变为private,原protected变为private,原private保持不变。

14.4显示调用父类构造函数

  • 当创建子类对象时,会默认调用父类无参构造函数
  • 若有子类对象,也会默认调用子类对象的无参构造函数
class father
{
    public:
    	father()
        {
            cout << "I am father()" << endl;
        }
    	
    	father(string s)
        {
            cout << "I am father(s)" << s << endl;
        }
    
};

class child : public father
{
    public:
    	//child(string s): father(s) 
    	//如果进行显示调用,结果为:
    	//I am father(s)r1chie
    	//I am child(s)r1chie
    	child(string s)
        {
            cout << "I am child(s)" << s << endl;
        }
    
};

int main()
{
    child c1("r1chie");
    
    return 0;
}

输出结果:
I am father()
I am child(s)r1chie

14.5父子间的同名变量和同名函数

  • 子类可以定义父类中的同名变量和函数
  • 子类的成员变量和函数会隐藏父类的同名函数和变量
  • 父类中的同名成员变量和函数依旧存在子类中
  • 通过作用域分辨符::才可以访问父类中的同名成员变量和函数
class father{
  public:
    int mval;
    father()
    {
        mval = 999;
    }
    int add(int i)
    {
        mval += i;
    }
    
};

class child : public father{
    public:
    	int mval;
    child()
    {
        mval = 9999;
    }
    
    int add(int i , int j)
    {
        mval += i+j;
    }
};

int main()
{
    child c1;
    
    //c1.add(1); //编译报错,子类中存在add函数,会把父类的add函数隐藏
    c1.add(1,0);
    c1.father::add(1);
    
    cout << c1.mval <<endl;
    cout << c1.father::mval << endl;
    
    return 0;
}

输出结果:
10000
1000

14.6父子间的兼容

  • 子类对象可以直接赋值给父类对象

    • father f1;
      child c1;
      f1 = c1;
      
  • 子类对象可以初始化父类对象

    • child c1;
      father f1(c1);
      
  • 父类可以直接引用子类对象

    • child c1;
      father &f = c1;
      
  • 父类指针可以直接指向子类对象

    • child c1;
      father *pf = &c;
      

    以上这些操作,都是编译器把子类退化为父类,所以上面的这些父类对象只能访问父类中的成员变量和函数。

如果父类想要访问子类,只能通过强制转换,将父类对象转换为子类对象:

child c1;
father *pf = c1;
child *c2 = (static_cast)<child*>(pf);

14.7虚函数

为了引入虚函数,看这么一段程序:

class Base
{
  public:
    void fun1()
    {
        cout << "BASE" <<endl;
    }
};

class Child : public Base
{
    public:
      void fun1()
      {
          cout << "Child" << endl;
      }
};

void call(Base *p)
{
    p->fun1();
}

int main()
{
    Child c1;
    Base b1;
    
    call(&b1);
    call(&c1);
    
    return 0;
}

输出结果:
BASE
BASE

​ c1对象并没有调用自己的成员函数,而是调用b1的成员函数。这样就达不到C++多态的特性了,所以我们引入了虚函数virtual关键字

​ 把上面代码中Base类里的fun1函数加上virtual关键字:

class Base
{
  public:
    virtual void fun1()
    {
        cout << "BASE" <<endl;
    }
};

输出结果:
BASE
Child

​ 为什么加一个关键字virtual就实现了多态?在call函数中,虽然通过Base引用调用fun1函数,但实际上的确调用了Child::fun1(),编译器在编译阶段,并不知道将要传递给该函数的哪一个对象,无法保证在不用情况下执行不同的fun1方法。应该调用谁的fun1显然是在运行阶段决定的。这是使用多态的不可见逻辑实现的,而这种逻辑是在编译阶段提供的。

​ 编译器会给实现了虚函数的基类以及继承了基类的派生类各自创建了一个虚函数表,对象的前四个字节(与平台相关)存放的是虚函数指针,虚函数指针指向虚函数表。

​ 在这张虚函数表中,只会存放使用关键字virtual的函数

class Base
{
    //virtual function table  point 
    //			----> virtual function table
    //				----->fun1()   address:0x00001234
    //				----->fun2()   address:0x00009999
 public:
    virtual void fun1() 
    {}
    
    virtual void fun2() 
    {}
};

class Child
{
    //virtual function table  point 
    //			----> virtual function table
    //				----->fun1()   address:0x00004321
    //				----->fun2()   address:0x00009999
    virtual void fun1() 
    {}
};

14.7.1基类和派生类同名的虚函数表中指向的地址不同

如上面的函数:

​ 基类中的虚函数表中的fun1()的地址为0x00001234

​ 派生类中虚函数表中的fun1()的地址为0x00004321

14.7.2基类和派生类非同名的虚函数表中指向地址相同

如上面的函数:

​ 基类中的虚函数表中的fun2()的地址为0x00009999

​ 派生类中虚函数表中的fun2()的地址为0x00009999

14.8虚析构函数

虚析构函数通常是用作一些资源释放来使用。

一个例子:

class Base
{
  public:
    Base()
    {
        cout << "BASE" <<endl;
        //申请资源
    }
	
	~Base()
	{
		cout << "~BASE()" << endl;
        //释放资源
	}
};

class Child : public Base
{
    public:
      Child()
      {
          cout << "Child" << endl;
          //申请资源
      }
	  
	   ~Child()
	  {
	  	cout << "~Child()" << endl;
        //释放资源
	  }
};



int main()
{
	Base *p = new Child();
    delete p;
	
    return 0;
}

输出结果:
BASE
Child
~BASE()

​ 很明显,Child申请了资源,使用delete p却没有执行到Child的析构函数,没有把Child申请的资源给释放掉。那么就可以在基类的析构函数前加virtual关键字,就可以解决这个问题。

构造函数不能成为虚函数

15.抽象类

  • 一种只能定义类型,而不能产生对象的类
  • 只能被子类继承,且抽象类的相关成员函数没有完整的体现,用来被子类重写

15.1纯虚函数

为了避免使用抽象类创建对象,可以使用纯虚函数来避免

  • 纯虚函数只需要声明函数名,不用实现函数内容,通过子类去实现
  • 当类中有纯虚函数时,该类就无法创建对象,因为纯虚函数里面没有具体内容,所以这个类便成为抽象类
  • 如果子类没有实现纯虚函数,则子类也会成为抽象类

纯虚函数只需要在函数名前加上virtual关键字,尾部加上 =0 就可以了

class r1chie
{
  public:
    virtual void fun() = 0;//纯虚函数
    
};

15.2接口

  • 类中没有成员变量
  • 所有成员函数都是公有的,并且都是纯虚函数
  • 接口是一种特殊的抽象类
//接口
class wifi 
{
    public:
    	virtual void openwifi() = 0;
    	virtual void closewifi() = 0;
    	virtual void scanwifi() = 0;
    
};

//子类实现接口
class r1chie : public wifi
{
    public:
    	r1chie()
        {
            cout << "r1chie()" << endl;
        } 
    
    	void openwifi()
        {
            cout << "openwifi" << endl;
        }
    
    	void closewifi()
        {
           	cout << "closewifi" << endl;
            
        }
    
    	void scanwifi()
        {
            cout << "scanwifi" << endl;
        }
    
};

int main()
{
    r1chie r1;
    r1.openwifi();
    r1.closewifi();
    r1.scanwifi();
    return 0;
}

16.泛型编程

在以前写一个swap函数:

int swap(int& i, int& j)
{
    int temp = i;
    i = j;
    j = temp;   
}

​ 不过这个函数只能支持int类型的交换,其他类char、double、long,都不能重复使用这个函数,能不能写一个支持所有类型的函数,答案是可以的,C++引入了泛型编程的概念。

16.1函数模板

  • 一种特殊的函数,可通过不同类型进行调用
  • 函数模板是C++中重要的代码复用方式
  • 通过template关键字来声明使用模板
  • 通过typename关键字来定义模板类型

16.1.1 单参数函数模板

下面来改造swap函数:

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

有两种调用函数模板的方法,一种是自动调用,一种是显示调用。

int main()
{
    int a = 1;
    int b = 2;
    float c = 11.1;
    float d = 12.1;
    
    swap(a,b); //自动调用,由编译器去检查类型
    swap<float>(c,d); //显示调用,由我们去告诉编译器该使用的类型
    
    cout << "a:" << a << endl;
    cout << "b:" << b << endl;
	cout << "c:" << c << endl;
	cout << "d:" << d << endl;
    
    return 0;
}

输出结果:
a:2
b:1
c:12.2
d:11.1

例子2,排序:


template <typename T>
void sort(T a[],int len)
{   
    for(int i = 1; i < len ;i++)
    for(int j = 0; j < i; j++)
	if(a[i] < a[j])
    {
        T t = a[i];
        a[i] = a[j];
        a[j] = t;
    }             
}

template <typename T>
void println(T a[],int len)
{
    for(int i = 0; i < len; i++)
    cout << a[i] << ", ";
    
    cout << endl;    
}

int main()
{
    int a[6] = {11,10,12,15,13,16};
    string s[6]= {"acv","aba","vas","bas","css"};
    
    sort(a,6);
    sort(s,6);
    
    println(a,6);
    println(s,6);
    
    return 0;
    
}

输出结果:
10, 11, 12, 13, 15, 16, 
aba, acv, bas, css, vas, 

为什么函数模板能够执行不同的类型参数?

  • 编译器对函数模板进行了两次编译

  • 第一次编译是检查模板有没有问题

  • 第二次编译就会去找到调用函数模板的代码,并通过参数生成新的函数

16.1.2 多参数函数模板

​ 工程中,一般都将返回值参数作为第一个模板类型

  • 如果返回值参数作为了模板类型,则必须指定返回值模板的类型,因为编译器无法推导返回值的类型
  • 可以从左往右,部分指定类型参数
template < typename T1,typename T2,typename T3>
T1 add(T2 a,T3 b)
{
    return static_cast<T1>(a+b);
}

int main()
{
    int a = add<int,int,float>(1,1.5);
    float b = add<float>(1.1,1.1);
    
    cout << a << endl;
    cout << b << endl;
    
    return 0;
}

输出结果:
2
2.5

16.1.3 重载函数模板

  • 函数模板可以像普通函数一样被重载
  • 函数模板不接受隐式转换
  • 当有函数模板,以及普通函数重载时,编译器会优先考虑普通函数
  • 如果普通函数的参数无法匹配,编译器会尝试进行隐式转换,若转换成功,就调用普通函数,若转换失败,就调用模板函数。
  • 可以通过空模板实参列表来限定编译器只匹配函数模板
int r1 = Max(1,2);

//限定编译器只匹配函数模板
double r2 = Max<>(0.5,0.8); 

例子:

template<typename T>
T Max(T a, T b)
{
    cout << "T Max(T a, T b)" << endl;
    return a > b ? a : b;
}

template <typename T>
T Max(T *a, T *b)
{
    cout << "T Max(T *a, T *b)" << endl;
    return *a > *b ? *a : *b;
    
}

int Max(int a,int b)
{
    cout << "int Max(int a, int b)" << endl;
    return a > b ? a : b;
}

int main()
{
    int a = 0;
    int b = 1;

    cout << Max(a,b) << endl;     //优先考虑普通函数
    cout << Max<>(a,b) << endl;   //使用空模板来限定编译器只匹配函数模板
    cout << Max(1.2,1.3) << endl; //普通函数与重载模板函数都不满足,那么就会选择模板函数
    cout << Max(&a,&b) << endl;   //普通函数与模板函数都不满足,那么就会选择重载模板函数
    cout << Max('b',100) << endl; //函数模板不接受隐式转换,但是普通函数可以隐式转换
    return 0;
}

输出结果:
int Max(int a, int b)
1
T Max(T a, T b)
1
T Max(T a, T b)
1.3
T Max(T *a, T *b)
1
int Max(int a, int b)
100

16.2 类模板

  • 定义对象时,必须指定类模板类型,因为编译器无法推导类型
  • 使用具体类型,用< Type >来定义对象

16.2.1 单参数类模板


template <typename T>
class Operator
{
  public:
    T add(T a,T b)
    {
        return a+b;
    }
    
    T minus(T a,T b)
    {
        return a-b;
    }            
};

string operator - (string &l,string &r)
{
    return "Minus";
}

int main()
{
    Operator<int> ops1;    //定义对象时,必须指定类模板类型
    Operator<string> ops2; //定义对象时,必须指定类模板类型
    
    cout << ops1.add(1,2) << endl;
    cout << ops2.minus("r1chie","r1chie") << endl;
    
 	return 0;
}

输出结果:
3
Minus

16.2.2 多参数类模板

template <typename T1,typename T2>//多参数类模板
class Operator
{
    public:
    	void add(T1 a,T2 b);
    
};

template <typename T1,typename T2>//多参数类模板
void Operator<T1,T2>::add(T1 a, T2 b)
{
    cout < a+b << endl;   
}

int main()
{
    Operator<int , float> op1;
    op1.add(2,2.1);
    return 0;
    
}

输出结果:
4.1

16.2.3 类模板特化

  • 表示可以存在多个相同的类名,但是模板类型都不一致
  • 特化类型分为完全特化与部分特化
  • 完全特化就是显示指定类型参数,模板声明写成template<>,还要在类名后面指定参数
  • 部分特化表示通过特定规则约束类型参数,模板声明和类似,还要在类名后面指定参数
template <typename T1, typename T2>
class Operator
{
  public:
    void add(T1 a,T2 b)
    {
    	cout << "add(T1 a, T2 b)" << endl;    
    }
};

//完全特化
template<>
class Operator <int,int>
{
    public:
    	void add(int a,int b)
        {
            cout << "add(int a, int b)" << endl;
        }
};

//部分特化
template<typename T>
class Operator<T*,T*>
{
    public:
    	void add(T* a,T* b)
        {
            cout << " add(T* a, T* b)" << endl;
        }
};

int main()
{
    int a,b;
    Operator<int,int> op1;     //完全特化
    Operator<int* ,int*> op2;  //部分特化
    Operator<int,double> op3;  //类模板
    
    op1.add(1,2);
    op2.add(&a,&b);
    op3.add(1,1.2);
    
    return 0;
}

输出结果:
add(int a, int b)
add(T* a, T* b)
add(T1 a, T2 b)   

16.2.4 数值参数的类模板

  • 数值型模板参数必须在编译时被唯一确定
  • 变量(运行期间是可变的),浮点型(不精确)、类对象(可变)等不能作为模板参数

例子:求1+2+3...+100

template <int N>
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 <<"1+2+3..+100=" << Sum<100>::Value << endl;
    return 0;
}

17.单例类

​ 指在整个系统生命周期中,一个类最多只能有一个实例存在,使得该实例的唯一性(实例是指一个对象指针),比如统计在线人数。

  • 将构造函数的访问属性设置为private
  • 提供一个GetInstance()静态成员函数,只能供用户访问唯一一个实例
  • 定义一个静态成员指针,用来供用户获取
  • 重载 = 赋值操作符以及拷贝构造函数,并设为private,避免对象间拷贝、赋值

17.1 懒汉式

  • 指代码运行后,实例并不存在,只有当需要的时候才去创建实例,适合单线程
class r1chie
{
  static r1chie* m_pInstance;
    
  r1chie()
  {}
    
  r1chie(const r1chie& r)
  {}
    
  r1chie& operator =(const r1chie& r)
  {}
    
  public:
    static r1chie* getInstance()
    {
        if(m_pInstance == NULL)
         m_pInstance = new r1chie;
        
 		 return m_pInstance;
    }
    
    void print()
    {
        cout << m_pInstance << endl;
    }
    
};

r1chie* r1chie::m_pInstance = NULL;

int main()
{
    r1chie *r1 = r1chie::getInstance();
    r1chie *r2 = r1chie::getInstance();
    r1chie *r3 = r1chie::getInstance();
    
    r1->print();
    r2->print();
    r3->print();
        
    return 0;
}


输出结果:
0x1abfe70
0x1abfe70
0x1abfe70    
  

17.2 饿汉式

  • 指代码运行后,实例自动创建,当需要时直接调用即可,适合多线程
class r1chie
{
    static r1chie* m_pInstance;
    
    r1chie()
    {}
    
    r1chie(const r1chie& r1);
    
    r1chie& operator = (const r1chie& r1);
    
    public:
    	static r1chie* getInstance()
        {
            return m_pInstance;
        }
    
    
    	void print()
        {
            cout << m_pInstance << endl;
        }
    
    
};

r1chie* r1chie::m_pInstance = new r1chie;

int main()
{
    r1chie* r1 = r1chie::getInstance();
    r1chie* r2 = r1chie::getInstance();
    r1chie* r3 = r1chie::getInstance();

    r1->print();
    r2->print();    
    r3->print();
}

输出结果:
0xdc0e70
0xdc0e70
0xdc0e70

17.3 单例类模板

  • 实现单例类模板
//单例模板
template <typename T>
class r1chie
{
    private:
    	static T* m_pInstance;
    	
    	r1chie()
        {}
    
    	r1chie& operator = (const r1chie& r);
    
    	r1chie(const r1chie& r);
    
    public:
    	static T* getInstance()
        {
           return m_pInstance;
        }
    
    	void print()
        {
            cout << m_pInstance << endl;
            
        }
    
};

template <typename T>
T* r1chie<T>::m_pInstance = new T;

class Test
{
    //使用友元
    friend class r1chie<Test>;
  	
    Test()
    {
        
    }
    Test& operator = (const Test& t);
    Test(const Test& t);
    
    public:
    	void print()
        {
            cout << "print" << endl;
        }
    
};


int main()
{
	Test *p = r1chie<Test>::getInstance();     
    
	p->print();
    
    return 0; 
}

18.异常处理

  • 在try语句中,使用throw语句抛出异常,后面有catch语句处理异常。
try
{
    //在这里面抛出int类型的异常
    throw 1; 
}
catch(int i) //捕获int类型的异常
{
    cout << "catch int:" << i << endl;
}

输出:
catch int:1
  • catch语句允许被重载,在try语句的后面可以有多个catch
  • 不同类型的异常由不同catch语句捕获,顺序从上到下严格匹配,不会进行隐式转换
throw 1;             //int类型catch捕获
throw 1U;            //unsigned int类型捕获
throw 1.5;           //double类型catch捕获
throw 1.5f;          //float类型catch捕获
throw 'A';           //char类型catch捕获
throw 'ABC';         //char const* 类型catch捕获
throw string("ABC"); //string类型catch捕获
  • 可以使用catch(...)来捕获没有定义到的异常
try
{
    throw 'a';
}
catch(int i)
{
    cout << "int" << endl;
}
catch(...)
{
    cout << "exception handling " << endl;
}

输出结果:
exception handling 
  • 如果当前函数没有catch能捕获,当它返回到下一级函数去找到catch

  • 可以在catch捕获到的异常再次抛出

void fun()
{
	try
	{
		throw 1;
	}
	catch(...)
	{
		cout << "..." << endl;
		throw; //把异常抛出
	}
}

int main()
{
	try
	{
        //没有找到合适的catch,返回到下一级寻找catch
		fun();
        
	}
	catch(int i)
	{
		cout << "int :" << i << endl;
	}

    return 0; 
}
输出结果:
...
int:1 

18.1 C++标准库异常类

标准库中的异常都是从execption类派生的,头文件<stdexcept>

exception类主要有两个分支:logic_error、runtime_error

18.1.1 logic_error

用于程序中避免的逻辑错误,在程序运行之前,就能被检测到

logic_error派生了下面几个类:

  • domain_error(const string&) 专业领域内的范畴
  • invalid_argument(const string&) 无效参数,比如对unsigned类型的值进行负数操作
  • length_error(const string&) 长度异常。比如字符串附加太多字符
  • out_of_range(const string&) 超出范围,比如数组下标越界

以上类都有一个what()成员函数,用来返回一个字符串类型的错误信息

18.1.2 runtime_error

常用于程序中无法避免的恶性错误,只有在程序中运行时才能被检测

runtime_error类派生了以下几种类:

  • range_error(const string&) 内部计算时发生区间错误
  • overflow_error(cosnt string&) 算数运算发生上溢出
  • underflow_error(const string&)算数运算发生下溢

以上类都有一个what()成员,用来返回一个字符串类型的错误信息

template <typename T,int N>
class Array
{
    T ma[N];
    public:
    	Array()
        {
            for(int i = 0; i < N;i++)
             ma[i] = 0; 
        }
    
    	T& operator[] (int index)
        {
            if(index >= N)
            {
                throw out_of_range("T& operator[] (int index)")
            }
            else
            {
                return ma[index];
            }
        }
    
};


int main()
{
    Array<int,5> a;
    try
    {
        //a.operator[] (100)  = 10
        a[100] = 10;
    }
    catch(out_of_range& exc)
    {
        cout << exc.what() << endl;
        cout<< " Line: " << __LINE__ <<", Function: "<< __FUNCTION__ << endl;
    }
    
    return 0;
}

输出结果:
T& operator[] (int index)
 Line: 41, Function: main

posted @ 2021-01-27 13:37  R1chie  阅读(194)  评论(0编辑  收藏  举报