1.头文件与类声明
1.1 C++代码的的基本形式
.h(headerfiles) ClassDeclearation声明
.cpp
.h(header files)标准库
1.2 头文件的布局
complex.h
#ifndef __MYCOMPLEX__
#define __MYCOMPLEX__
//防卫式声明
class complex; //前置声明
complex&
__doapl (complex* ths, const complex& r);
complex&
__doami (complex* ths, const complex& r);
complex&
__doaml (complex* ths, const complex& r);
class complex //类声明
{
public:
complex (double r = 0, double i = 0): re (r), im (i) { }
complex& operator += (const complex&);
complex& operator -= (const complex&);
complex& operator *= (const complex&);
complex& operator /= (const complex&);
double real () const { return re; }
double imag () const { return im; }
private:
double re, im;
friend complex& __doapl (complex *, const complex&);
friend complex& __doami (complex *, const complex&);
friend complex& __doaml (complex *, const complex&);
};
complex::function... //类定义
#endif
防卫式声明的意义:防止一个头文件被引用多次,而导致重复定义;即若不曾经定义才定义。
1.3 class的声明
一个类由两部分组成,即class head,class body,有些函数可以在class body中定义,另一些在body之外定义。
1.4 类模板class template(模板)
例1.
template<typename T>
class complex
{
...
private:
T re,im;
...
};
用法:
T可以替换成使用者使用的数据类型
例2.
{
complex<double>c1(2.5,1.5);
complex<int>c2(2,6);
...
}
2.构造函数
1.inline函数:即内联函数,函数在class body中定义;
补充:函数调用需要时间和空间开销,调用函数实际上将程序执行流程转移到被调函数中,被调函数的代码执行完后,再返回到调用的地方。这种调用操作要求调用前保护好现场并记忆执行的地址,返回后恢复现场,并按原来保存的地址继续执行。对于较长的函数这种开销可以忽略不计,但对于一些函数体代码很短,又被频繁调用的函数,就不能忽视这种开销。引入内联函数可以提高程序的运行效率。
2.access level(访问级别)
public:公开部分;
private:私人部分,不能直接读取private内的数据;
3.constructor(ctor,构造函数)
构造函数的功能主要用于在类的对象创建时定义初始化的状态,构造函数不能被直接调用,必须通过new运算符在创建对象时才会自动调用;而一般的方法是在程序执行到它的时候被调用的。
例3.
class complex
{
public:
complex(double r=0,double i=0) //默认实参
: re(r),im(i) //初值列,初始值,也可写为{re=r;im=i;},但是会降低程序的效率。
{}
...
};
4.ctor(构造函数)可以有很多个-overloading(重载)
构造函数的重载:构造函数具有相同的名字,而参数的个数或参数类型不相同。
例4.
class complex
{
...
complex (double r=0,double i=0)//(I)
: re(r),im(i)
{}
complex ()
: re(r),im(i) //(II)
{}
...
double real() const {return re;}//(a)
}
void real (double r) const {re=r;}//(b)
...
1) 对于(I),(II)两个函数(I)有默认值,(II)如例时会发生冲突。
2) 对于(a),(b)两个函数,real()函数编译后
?real@Complex@@QBENXZ //(a)
?real@Comeplex@@QAENABN@N //(b)
在编译器中实际名称不一样。
注:在编译器内部函数的具体名称取决于编译器。
5.constructor构造函数放在private区
对于在private区的函数,只能被本类“内部”的其他函数说调用。
例5.
...
{
complex c1(2,1); //当构造函数在private区时,直接建立对象时程序会出错。
complex c2;
...
}
补充:如果将构造函数声明为private,那只能这个类的“内部”的函数才能构造这个类的对象。
3.参数传递
1.pass by value/pass by reference
pass by value:按值传递,传递的参数是按值的拷贝传递。
pass by reference:传递的参数是按引用进行传递,其实传递的引用的地址,也就是变量所对应的内存空间的地址。
例6.
class complex
{
public:
complex(double r=0,double i=0) //double无‘&’符号,即为by value.
: re(r),im(i)
{}
complex operator +=(const complex&);//这里的const complex&就是pass by reference.
...
返回值传递
class complex
{
...
complex& operator += (const complex&); //返回值return by reference.
double real () const{return re;} //返回值return by value.
double imag () const{return im;}
...
};
2.友元
友元可以让其他的函数调用private中的数据。
补充:相同class的各种object互为友元。
例7.
class complex
{
public:
int func(const complex& param)
{ return param.re+param.im;}
pravate:
...
};
此时,若输入:
complex c1(2,1);
complex c2;
c2.func(c1);
因为c1,c2是相同class的不同对象,所以互为友元,所以此时程序能够正常运行。
3.class body外的各种定义
哪些情况可以pass by reference/return by reference
例8.
...
inline complex&
_doapal (complex* ths,const complex& r)
{
ths->re += r.re; //第一参数会被改动
ths->im =+ r.im; //第二参数不会被改动
return *ths;
}
...
此时,即可以return by reference。
引用作为返回值的时候需要注意,绝不能返回 local 对象的reference ,这时必须要以传值的形式作为返回值。
4.补充:(这里听课的时候听的不是很明白,查阅了一下相关的知识,在这做一点补充,以下内容引用于http://www.cnblogs.com/yanlingyin/ 一条鱼@博客园 2011-12-7)
值传递:
形参是实参的拷贝,改变形参的值并不会影响外部实参的值。从被调用函数的角度来说,值传递是单向的(实参->形参),参数的值只能传入,不能传出。当函数内部需要修改参数,并且不希望这个改变影响调用者时,采用值传递。
指针传递:
形参为指向实参地址的指针,当对形参的指向操作时,就相当于对实参本身进行的操作。
引用传递:
形参相当于是实参的“别名”,对形参的操作其实就是对实参的操作,在引用传递过程中,被调函数的形式参数虽然也作为局部变量在栈中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地址。被调函数对形参的任何操作都被处理成间接寻址,即通过栈中存放的地址访问主调函数中的实参变量。正因为如此,被调函数对形参做的任何操作都影响了主调函数中的实参变量。
#include<iostream>
using namespace std;
//值传递
void change1(int n){
cout<<"值传递--函数操作地址"<<&n<<endl; //显示的是拷贝的地址而不是源地址
n++;
}
//引用传递
void change2(int & n){
cout<<"引用传递--函数操作地址"<<&n<<endl;
n++;
}
//指针传递
void change3(int *n){
cout<<"指针传递--函数操作地址 "<<n<<endl;
*n=*n+1;
}
int main()
{
int n=10;
cout<<"实参的地址"<<&n<<endl;
change1(n);
cout<<"after change1() n="<<n<<endl;
change2(n);
cout<<"after change2() n="<<n<<endl;
change3(&n);
cout<<"after change3() n="<<n<<endl;
return true;
}
输出结果:
可以看出,实参的地址为0x22ff44,采用值传递的时候,函数操作的地址是0x22ff20并不是实参本身,所以对它进行操作并不能改变实参的值再看引用传递,操作地址就是实参地址 ,只是相当于实参的一个别名,对它的操作就是对实参的操作。
指针传递和引用传递的区别:
引用的规则:
(1)引用被创建的同时必须被初始化(指针则可以在任何时候被初始化)。
(2)不能有NULL引用,引用必须与合法的存储单元关联(指针则可以是NULL)。
(3)一旦引用被初始化,就不能改变引用的关系(指针则可以随时改变所指的对象)。
指针传递的实质:
指针传递参数本质上是值传递的方式,它所传递的是一个地址值。值传递过程中,被调函数的形式参数作为被调函数的局部变量处理,即在栈中开辟了内存空间以存放由主调函数放进来的实参的值,从而成为了实参的一个副本。值传递的特点是被调函数对形式参数的任何操作都是作为局部变量进行,不会影响主调函数的实参变量的值。(这里是在说实参指针本身的地址值不会变)
4.操作符重载
1.成员函数的操作符重载
C++有许多内置的数据类型,包括int,char,double等,每一种类型都有许多运算符,例如加,减,乘,除等。当用户定义了类的对象时,两个对象之间是不能进行这些操作的,比如class类的对象a+b,这样的语句如果没有重载+运算符就会出错。
例9.
...
inline complex&
_doapl(complex* ths,const complex& r)
{
ths->re += r.re;
ths->im += r.im;
return *ths;
}
inline complex&
complex::operator += (this,const complex& r)
{
return_dopal(this,r); //this是隐藏参数,在编译器中存在,但无法被用户看到
}
...
如若输入:
complex c1(2,1);
complex c2(5);
c2+=c1;
这里c2对应的就是隐藏参数this,c1对应r。
当操作符重载函数作为类的成员函数时,操作符重载函数的参数会比作为友元或者独立于类的操作符重载函数少一个参数,因为操作符重载类成员函数把调用该函数的对象作为函数的第一个参数,也就是隐含的this指针指向调用该函数的第一个对象,所以会少一个参数。
2.操作符重载,非成员函数
例10.
class complex
//这里只能ruturn by value,不能return by reference,因为返回的值是一个local object,当函数调用完成以后,在内存空间的那个“值”随之释放了。
{
...
};
...
inline complex
operator +(const complex& x,const complex& y)
{
return complex(real(x)+real(y),imag(x)+imag(y); //对应c2=c1+c2类型
}
inline complex
operator +(const complex&x,const complex& y)
{
return complex(real(x)+y,imag(x)); //对应c2=c1+常数类型
}
inline complex
operator +(const complex& x,const complex& y)
{
return complex(y+real(x),imag(x); //对应c2=常数+c1类型
}
...
3.使用typename()创建临时对象
例11.
...
inline complex
operator +(double x,const complex& y)
{
return complex(x+real(y),imag(y));
}
...
例子中complex(x+real(y),imag(y))创建了一个complex型的临时变量。这种临时变量只有瞬间的生命周期使用完成后,在内存中所占用的区域随即释放。因此此时也不能return by reference。
4.‘>>’,‘<<’操作符的重载
例12.
inline complex
conj (const complex& x)
{
return complex (real(x),-imag(x));
}
#include<iostream>
ostream&
operator <<(ostream& os,const complex& x)
{
return os<<'('<<real(x)<<','<<imag(x)<<')';
}
为了与标准库IO操作一致,重载 << 操作符函数应把ostream&作为其第一个参数,对类类型const对象的引用作为第二个参数,并返回对ostream形参的引用。
‘<<’操作符应定义为非成员函数。因为对于类的成员函数,左操作数为该类类型的对象,这样只能将第二个参数作为ostream&,使用时只能c1<