C++ Primer读书笔记 (1)

C++ Primer读书笔记 (1)

7. 类

1.构造函数

构造函数的名字和类名相同

template<typename T> Class CComlpex{
private:
    T realVal;
	T imagVal;
public:
    CComplex():realVal(0),imagVal(0) {}
	CComplex(const T rv, const T iv):
	    realVal(rv), imagVal(iv) {}
};

尽量使用参数列表形式的成员初始化,这样可以显式地调用成员变量的构造函数。虽然当成员函数是基本类型的时候,赋值初始化与直接初始化时等价的。

当成员函数定义在类的内部的时候,成员函数是隐式inline(内联)的。

不带参数或者可以不提供参数(例如为所有参数都提供了默认值)的构造函数为默认构造函数。

CComplex<double> z;

调用默认构造函数。注意,如果不考虑模板,CComplex z();也不是调用默认构造函数,而是声明了默认构造函数,不能起到创建对象的作用。

如果不显式提供,编译器会隐式合成一个默认构造函数,即调用每个成员的默认构造函数。如果有其他构造函数,编译器不再合成默认构造函数,此时对默认构造函数的隐式调用将报错。

2.类拷贝

一个以自身类型的const引用为参数的构造函数为拷贝构造函数

CComplex(const CComplex &z)
{
    realVal = z.realVal;
	imagVal = z.imagVal;
}

当执行赋值初始化的时候,系统会调用拷贝构造函数,例如

CComplex<double> z1(1.0,1.0);
CComplex<double> z2 = z1; // 调用拷贝构造函数

如果不显式提供,则系统将隐式合成一个拷贝构造函数。隐式的拷贝构造函数只能实现所有的成员的直接拷贝(即调用它们的拷贝构造函数),当对于基本类型的时候,这是没有问题的。但是有些情况会出现问题。

3.隐式转换

当一个构造函数只有一个参量的时候,可以用于隐式类型转换,例如

CComplex(const T z)
{
   realVal = z;
   imag = 0;
}

此时可以利用该构造函数进行隐式转换,注意隐式类型转换只能使用一步。

CComplex<double> z = 1.0; //double隐式转换成临时的CComlpex<double>,再赋给z

在向CComplex<T>类型(或对应的const、引用、const引用)的形参传递T型的实参时,以可以发生上述隐式类型转换。

如果想禁止隐式类型转换,可以用explicit来修饰构造函数

explicit CComplex(const string &str);

此时不可以使用隐式转换,但可以使用static_cast等显式类型转换。

string str = "1+1i";
CComplex<double> = str; //Error! 
CComplex<double> = static_cast<CComplex<double>>(str); //OK!

4.赋值

重载operator=运算符可以定义类的赋值函数,用于类之间的赋值,例如

CComplex<T>& operator=(const CComplex<T> &z) //operator= 应该定义成成员函数
{
    realVal = z.realVal;
	imagVal = z.imagVal;
	return *this;
}

当发生赋值的时候,编译器将调用赋值函数,例如

CComplex<double> z1(1,1);
CComplex<double> z2 = z1; //调用拷贝构造函数
CComplex<double> z3; //调用默认构造函数,创建空的z3
z3 = z1; //调用复制函数,即重载的operator=

注意区分赋值函数和拷贝构造函数,后者仅在初始化的时候用,前者在对已经定义过的实例赋值的时候用。

5.运算符重载

重载operator后面加运算符的函数可以实现运算符重载。单目运算符重载函数有一个形参,表示其操作数;双目运算符重载函数有两个形参,第一个形参和第二个形参分别表示其左操作数和右操作数。例如当不考虑template的情况下,对CComplex类的==运算符进行重载

bool operator==(const CComplex &lhs, const CComplex &rhs)
{
    return (lhs.realVal == rhs.realVal) &&
	       (lhs.imagVal == rhs.imagVal);      
}

这里面有一个问题,因为上述operator==函数不是CComplex的成员函数,因此不能访问private对象realValimagVal。解决的方法有两种,一是将operator==定义成成员函数

bool CComplex::operator==(const CComplex &rhs)
{
    return (realVal == rhs.realVal) &&
	       (imagVal == rhs.imagVal);  
}

注意当operator==被定义成成员函数之后,它的左操作数形参省略不写,唯一的参量是其右操作数参量。

另一种解决方法是在类中将operator==声明为CComplex类的友元函数,方法如下

class CComplex
{
    friend bool operator==(const CComplex&, const CComplex&);
private:
    //...
};

6.友元

在类的内部声明一个可以声明一个类、函数、类的成员函数为该类的友元。此时友元类的成员函数、友元函数可以访问该类的私有对象,例如

class CComplex
{
    friend bool operator==(const CComplex&, const CComplex&);
private:
    double realVal;
	double imagVal;
};

bool operator==(const CComplex&,const CComplex&)
{
    return (lhs.realVal == rhs.realVal) &&
	       (lhs.imagVal == rhs.imagVal);
}// operator==是CComplex的友元函数,可以访问CComplex的private成员

对类进行运算符重载的时候,既可以声明成成员函数,也可以声明成友元函数。但是值得注意的是,成员函数必选满足所重载的运算符的左操作数是类类型,例如operator<<的左操作数类型是ostream&,不是类类型,因此不能声明成成员函数,只能声明成友元函数。

一般来说,单目运算符通常被声明成成员函数,双目运算符被声明成友元函数。

7.模板

使用template<typename T>可以定义一个类型模板,例如

template<typename T> class CComplex
{
private:
    T realVal;
	T imagVal;
};

是一个模板类。该类实例化的时候,要显式地给出模板参数列表,例如

CComplex<double> z;

又例如

template<typename T> bool isEqual(const CComplex<T>&, const CComplex<T>&);

是一个模板函数,在调用的过程中

CComplex<double> z1(1,1);
CComplex<double> z2(2,-2);
bool res = isEqual(z1,z2); //让模板函数通过函数实参推断模板类型参数
bool res2 = isEqual<>(z1,z2); //同上,通过一个空的参数列表显式指出isEqual是一个模板函数
bool res3 = isEqual<double>(z1,z2);//直接给出模板类型参数,不通过函数实参推断

注意,模板函数的声明和定义必须放在同一个文件中,如下的形式会被编译报错

// add.h
template<typename T> bool isEqual(const CComplex<T>&, const CComplex<T>&);
// add.cpp
template<typename T> bool isEqual(const CComplex<T>& z1, const CComplex<T>& z2)
{
    //...
}

因此模板函数的定义通常都放在.h文件中。

当把模板函数声明为友元函数的时候,要在类前先定义模板函数,并且在友元函数声明的过程中,要通过空的参数列表<>来显式指定这是一个模板函数。

template<typename T> class CComplex; //声明类,供模板函数声明用
template<typename T> CComplex<T> operator+(const CComplex<T>&, const CComplex<T>&); //声明模板函数

template<typename T> class CComplex //定义类
{
    friend CComplex<T> operator+<>(const CComplex<T>&,const CComplex<T>&);
	//声明友元函数,并显式地使用<>表明这是一个模板函数
private:
    //...
};
template<typename T> CComplex<T> operator+(const CComplex<T>&, const CComplex<T>&) //定义模板函数
{
    //...
}

对于模板函数是运算符重载函数的时候,这种方法尤为必要。但是,可以把模板函数声明成成员函数来简化问题,或者可以声明成非成员非友元函数,但是调用其他友元函数就可以了,例如非成员非友元的operator+调用成员函数operator+=

posted @ 2016-02-19 15:01  anzhecheng  阅读(145)  评论(0)    收藏  举报