C++设计一个不含指针的类相对设计一个含指针的类来说简单一些,因为它不需要考虑一下三大因素(也叫Big Three):
析构函数(destructor)
拷贝构造函数(copy constructor)
拷贝赋值函数(copy assignment operator)
如果我们定义一个类的时候没有给出这3个函数,那么C++的编译器会自动生成四个缺省函数如下:
A(void); // 缺省构造函数
A(const A &a); // 缺省拷贝构造函数
~A(void); // 缺省析构函数
A & operate =(const A &a); // 缺省拷贝赋值函数
这些函数都是基于memberwise copy - 因为没有指针,所以不会涉及到同一块内存被多个对象指向的问题。析构的时候也不需要考虑内存的操作。
举例complex类如下:
class complex //class head
{
//class body
public:
complex (double r = 0, double i = 0): re (r), im (i) { }
complex& operator += (const complex&);
double real () const { return re; }
double imag () const { return im; }
private:
double re, im;
friend complex& __doapl (complex *, const complex&);
};
这里有几个需要注意的地方:
-
real()和imag()在类的体内定义,则自动成为inline函数。如果在体外定义,则需要显式声明inline才会成为inline函数。
-
friend函数本身不是类的一部分,friend是指此函数可以自由访问该类的private成员。friend也可以用在类上面,一个类的friend类的object可以访问该类的private成员。注意: 同一个类的各个object互为friend,这也是为什么拷贝构造函数和赋值构造函数可以实现的基础。
friend函数有什么好处呢?因为friend函数不需要通过class来访问,比用class函数来做效率更高。
- complex定义了自己的缺省构造函数。
complex (double r = 0, double i = 0): re (r), im (i) { }
所谓缺省构造函数是指对象的创建不需要参数就可调用的构造函数。C++标准规定,如果构造函数没有参数,或者构造函数的所有参数都有缺省值,则算作缺省构造函数。
如果complex没有定义自己的缺省构造函数,C++编译器会自动给它生成一个缺省构造函数类似于
complex() {}
注意该函数什么也不做。
如果complex的缺省构造函数为
complex(double r=0, double i=0): re®, im(i) {},
并且没有其他缺省构造函数,代码
complex c1;
会调用该构造函数,并用re和im的缺省参数。
如果complex的构造函数为
complex(double r, double i): re( r ), im( i ) {},
并且没有其他缺省构造函数,代码
complex c1;
会编译出错,因为编译器不知道把什么值赋给re和im。
如果complex的缺省构造函数为
complex() {},
并且没有其他缺省构造函数,代码
complex c1;
会调用该缺省构造函数。
如果complex 有两个缺省构造函数,
complex() {}
complex(double r=0, double i=0): re( r ), im( i ) {}
代码
complex c1;
编译会出错,因为编译器不知道调用那个缺省构造函数。
如果complex有以下两个构造函数,注意第一个为缺省构造函数,第二个不是。它们可以共存。
complex() {}
complex(double r, double i): re( r ), im( i ) {}
代码
complex c1;
编译会通过,编译器会采用complex() {}作为缺省构造函数。
如果complex没有定义拷贝构造函数,那么
complex c1=complex();
和
complex c1 = c2;
都会调用complex的缺省拷贝构造函数(也就是memberwise copy)。注意第一个例子里面的complex()会导致调用缺省构造函数生成一个complex的临时对象,然后代码再调用comlex的缺省拷贝构造函数进行memberwise copy。第二个例子则只需调用缺省拷贝构造函数一次。
如果complex没有定义拷贝赋值函数,那么
c1 = c2;
会调用complex的缺省拷贝赋值函数。
- 下面几种写法要特别注意它们的区别:
complex c1(); 声明一个名为c1的函数, 该函数返回complex类型。注意这里不是创建complex 类的对象! 切记!!!
complex c1; //创建一个complex的一个名为c1的obj, 并且会调用complex 类中的缺省构造函数,其缺省参数为(0,0).
complex(); //创建一个complex的临时对象,此处会调用complex 类中的缺省构造函数,同样,int()也是创建一个int的临时对象
complex c1=complex(); //创建一个complex的临时对象,并赋给c1,此处会调用缺省构造函数和缺省拷贝构造函数
complex c1=complex(3.0, 4.0); ** //创建一个complex的临时对象,并赋给c1,此处会调用构造函数和缺省拷贝构造函数**
注意这里也可以写成complex c1(3.0, 4.0), 会调用构造函数。
complex *p=new complex();//创建一个complex的obj,这里会调用complex类中的缺省构造函数,并用指针p指向它。
注意,这两种写法都可以:
complex c1 = complex(3.0, 4.0); //构造函数和缺省拷贝构造函数
complex c1(3.0, 4.0); //构造函数only ?
- complex重载了+=操作符。操作符重载可以看成是一个特殊的函数,给C++编程带来很大便利。
操作符重载可以是类成员函数,也可以是非类成员函数。如果是object自身与complex的其它object相加,可写成类成员函数,如上面的这种情况; 但如果不是object自身与complex其他object相加,则不能写成类成员函数。
如果是成员函数的话,默认第一个参数是this指针,通常不写。注意:操作符重载一定是作用在左边的操作符。
-
上面operator+重载函数参数采用传引用。 对于函数入口参数,尽量用传引用,因为传值的话,value会放进stack,应该尽量少用。
另外,对于函数返回值也是尽量返回reference,但如果函数返回的是在栈上分配的一个local变量或object,则不能返回reference,因为该local变量或obj已经被删除。 -
上面operator+的入口参数加了const。对于函数入口参数,如果函数不会改变这个参数,则该参数可以加const。
另外,如果某类函数不会改变函数的数据成员,则可以在函数名后加const,比如上面的real()和imag()函数。 -
C++ 的构造函数和析构函数都没有返回值!切记!
-
拷贝构造函数的参数一定要加&(即传引用),如果是传值,形参复制到实参的时候又会调用拷贝构造函数,这样就形成永无止境的递归调用而导致栈溢出。
一个Complex类的实现例子如下:
#include <iostream>
#include <complex>
using namespace std;
class MyComplex {
public:
MyComplex(double re = 0, double im = 0) : real(re), imag(im) {}
MyComplex(const MyComplex &c) : real(c.real), imag(c.imag) {}
MyComplex & operator = (const MyComplex &c) { real = c.real; imag = c.imag; }
MyComplex & operator += (const MyComplex &c);
MyComplex & operator -= (const MyComplex &c);
MyComplex & operator *= (const MyComplex &c);
MyComplex & operator /= (const MyComplex &c);
MyComplex operator + (const MyComplex &c);
MyComplex operator - (const MyComplex &c);
MyComplex operator * (const MyComplex &c);
MyComplex operator / (const MyComplex &c);
double get_real() { return real; };
double get_imag() { return imag; };
private:
double real, imag;
friend ostream & operator<<(ostream &out, const MyComplex &c);
};
MyComplex& MyComplex::operator += (const MyComplex &c) {
this->real += c.real;
this->imag += c.imag;
return *this;
}
MyComplex& MyComplex::operator -= (const MyComplex &c) {
this->real -= c.real;
this->imag -= c.imag;
return *this;
}
MyComplex& MyComplex::operator *= (const MyComplex &c) {
// this->real = this->real * c.real - this->imag * c.imag;
double re = this->real * c.real - this->imag * c.imag;
this->imag = this->real * c.imag + this->imag * c.real;
this->real = re;
return *this;
}
MyComplex& MyComplex::operator /= (const MyComplex &c) {
MyComplex temp(c.real, -c.imag);
MyComplex numerator = *this * temp;
double denominator = c.real * c.real + c.imag * c.imag;
//MyComplex res = MyComplex(numerator.real / denominator, numerator.imag / denominator);
this->real = numerator.real / denominator;
this->imag = numerator.imag / denominator;
return *this;
}
MyComplex MyComplex::operator + (const MyComplex &c) {
MyComplex res(this->real + c.real, this->imag + c.imag);
return res;
}
MyComplex MyComplex::operator - (const MyComplex &c) {
MyComplex res(this->real - c.real, this->imag - c.imag);
return res;
}
MyComplex MyComplex::operator * (const MyComplex &c) {
MyComplex res(this->real * c.real - this->imag * c.imag,
this->real * c.imag + this->imag * c.real);
return res;
}
MyComplex MyComplex::operator / (const MyComplex &c) {
MyComplex temp(c.real, -c.imag);
MyComplex numerator = *this * temp;
double denominator = c.real * c.real + c.imag * c.imag;
MyComplex res = MyComplex(numerator.real / denominator, numerator.imag / denominator);
return res;
}
ostream & operator<<(ostream &out, const MyComplex &c) {
out << "(" << c.real << "," << c.imag << ")";
return out;
}
int main() {
complex<double> c1(3.6, 7.2), c2(7.1, 9.5);
MyComplex myc1(3.6, 7.2), myc2(7.1, 9.5);
cout << "c1 = " << c1 << endl;
cout << "c2 = " << c2 << endl;
cout << "myc1 = " << myc1 << endl;
cout << "myc2 = " << myc2 << endl;
complex<double> d1 = c1 + c2;
MyComplex myd1 = myc1 + myc2;
cout << "d1 = " << d1 << endl;
cout << "myd1 = " << myd1 << endl;
complex<double> e1 = c1 - c2;
MyComplex mye1 = myc1 - myc2;
cout << "e1 = " << e1 << endl;
cout << "mye1 = " << mye1 << endl;
complex<double> f1 = c1 * c2;
MyComplex myf1 = myc1 * myc2;
cout << "f1 = " << f1 << endl;
cout << "myf1 = " << myf1 << endl;
complex<double> g1 = c1 / c2;
MyComplex myg1 = myc1 / myc2;
cout << "g1 = " << g1 << endl;
cout << "myg1 = " << myg1 << endl;
c1 += c2;
myc1 += myc2;
cout << "c1 = " << c1 << endl;
cout << "myc1 = " << myc1 << endl;
c1 -= c2;
myc1 -= myc2;
cout << "c1 = " << c1 << endl;
cout << "myc1 = " << myc1 << endl;
c1 *= c2;
myc1 *= myc2;
cout << "c1 = " << c1 << endl;
cout << "myc1 = " << myc1 << endl;
c1 /= c2;
myc1 /= myc2;
cout << "c1 = " << c1 << endl;
cout << "myc1 = " << myc1 << endl;
return 0;
}
注意:
To implement assign operator, default constructor is must
it will create a empty object using default constructor then assign all members from left hand side object to right hand side object. //这里好像写反了?
这是因为拷贝赋值函数必须要先用缺省构造函数生成一个缺省的对象(等号左边),然后再把等号右边的对象的members拷贝过来。