1.C++中的结构体
从c到c++迈出的第一步是允许将函数放入一个结构体。这一步是过程化到到面向对象质的变化。比如下边的例子:
注意:结构体中,如果没有明确访问特性,则成员是public。但在类中如果不指明,默认是private的。
//结构体定义,其中包含函数
struct DoubleArray
{
int low;
int high;
double *storage;
bool initialize(int lh,int rh);
bool insert(int index,double value);
bool fetch(int index,double &value);
};
//结构体函数实现
bool DoubleArray::initialize(int lh,int rh)
{
...
}
bool DoubleArray::insert(int index,double value)
{
......
}
bool DoubleArray::fetch(int index,double& value)
{
....
}
2.类的定义
2.1类的一般格式
class 类名 {
private:
私有数据成员和成员函数
public:
公有数据成员和成员函数
};
私有成员(private):只能由类的成员函数调用,私有成员被封装在一个类中,类的用户是看不见的。
公有成员(public):类的用户可以调用的信息,是类对外的接口
注意:
1.private 和public的出现次序可以是任意的。也可以反复出现多次。
2.成员还可以被说明为protected
3.数据成员一般是私有的
4.成员函数一般是公有的
5.成员函数实现时分解出的小函数是私有的
6.如果一个成员没有指名访问特性,该成员默认是private。但结构体中不是,参加结构体部分。
7.外部函数不能引用对象的私有成员
2.2 对象的定义
- 直接在程序中定义某个类的对象
存储类别 类名 对象列表;
DoubleArray arr1, arr2;
- 用动态内存申请的方法申请一个动态对象
Rational *rp;
Rp = new Rational; //这种初始化方法适合用于类的指针初始化,避免空指针
rp = new Rational[20];
delete Rp;
delete []rp;
与结构体一样,同类对象之间可以进行相互赋值。当把一个对象赋值给另一个对象时,所有的数据成员都会按位复制。如:
arr1=arr2;
2.3 this指针
成员函数操作的数据成员是本对象的数据成员。在c++中,不管创建多少个该类的对象,成员函数在内存中只有一个副本。所有的该类的对象共享这个副本。
每个成员函数都有一个隐藏的指向本类型的指针形参this,它指向当前调用成员函数的对象 。比如函数void create(int n, int d) { num = n; den = d;}
经过编译后,实际函数为:
void create(int n, int d)
{ this->num = n; this->den = d;}
通常,在写成员函数时可以省略this,编译时会自动加上它们。如果在成员函数中要把对象作为整体来访问时,必须显式地使用this指针。这种情况常出现在函数中返回一个对调用函数的对象的引用。
2.4 构造函数和析构函数
为什么要用构造函数和析构函数?
1.某些类的对象,必须在对它进行了初始化以后才能使用
2.对于某些类的对象在消亡前,往往也需要执行一些操作,做一些善后的处理。
3.初始化和扫尾的工作给类的用户带来了额外的负担,使他们觉得类和内置类型还是不一样。
4.用户希望使用类的对象就和使用内置类型的变量一样,一旦定义了,就能直接使用。用完了,由系统自动回收。
以上这些工作都由构造函数和析构函数完成了
2.4.1构造函数
使用构造函数可以完成为对象赋初值的任务,如果需要为某个类的对象赋初值,这个类就应该包含一个构造函数。需要注意的是平时写的其他成员函数都是有用户程序调用,而构造函数是由系统在定义对象时自动调用。
构造函数特点:
- 构造函数的名字必须与类名相同
- 构造函数可以有任意类型的参数,也可以不带参数,但不能具有返回类型。因此在定义构造函数时,不能说明它的类型,甚至说明为void类型也不行。
- 构造函数可以重载
构造函数的使用: 类名 对象名(实际参数表);
其中实际参数表必须和该类的某一个构造函数的形式参数表相对应。除非这个类有一个构造函数是没有参数的,那么可以用:类名 对象名;
这样的初始化调用的是不带参数的构造函数,称为默认的构造函数。一般每个类应该有一个缺省的构造函数 。如果在定义类时没有定义构造函数,编译器就会自动生成一个构造函数,这个构造函数没有参数且函数体为空,此时生成的对象的所有数据成员的初值都是随机数。但如果定义了构造函数,编译器就不再生成这个函数。
DoubleArray array(20,30);//对象定义和初始化同时完成,不再需要调用类似initialize()的函数初始化。
如果一个类需要即支持对象赋值,也可以不赋值,那么至少需要两个构造函数。一个是默认的构造函数,一个是带参数的构造函数。这样又使类看上很啰嗦,一种方法是在类中声明构造函数的原型时为形参指定默认值(注意是在函数声明处指定)。
DoubleArray(int lh=0,int rh=0);//构造函数在.h文件中这样声明
对象可能有多种不同的赋初值
构造函数的初始化列表
构造函数还有一个与普通函数不同的地方,就是可以包含一个构造函数初始化列表。构造函数初始化列表位于函数头和函数体之间。它以一个冒号开头,接着是一个以逗号分隔的数据成员构造列表,每个数据成员后边跟着一个放在圆括号中的对应于该数据成员的构造函数的实际参数。
DoubleArray :: DoubleArray(int lh, int rh)
: low(lh), high(rh) //注意没有分号
{ storage = new double [high - low + 1]; }
为什么要使用构造函数初始化列表?
- 有了初始化列表,可以使数据成员初始化和赋初值同时进行。提高了构造函数效率。
有些数据成员必须使用初始化列表才能赋值,比如const类型,自定义的类类型型等。常量只能在定义时对它初始化,而不能对它赋值。因此也必须放在初始化列表中。 某一个类的对象,可能无法直接用赋值语句在构造函数体中为它赋初值
2.4.2拷贝构造函数(复制构造函数)
在创建一个对象时,可以用一个同类的对象对其初始化。这是需要调用一个特殊的构造函数,称为拷贝构造函数。如果用户没有定义拷贝构造函数,系统会定义一个缺省的拷贝构造函数。该函数将已存在的对象原式原样地复制给新成员。
拷贝构造函数以一个同类对象引用作为参数,它的原型为:
类名(const 类名& 对象名);
比如:Rational(const Rational& obj ) { num=2*obj.num;den=obj,den; } Rational r1(1,2),r2(r1),r3=r1;
2.4.3 析构函数
析构函数在撤销对象时,完成一些善后工作,由编译系统自动调用。析构函数与构造函数名字相同,但它前面必须加一个波浪号(~)。析构函数没有参数,没有返回值,也不能重载。若定义类时没有定义析构函数,编译系统会自动生成一个缺省的空析构函数。
注意:
- 并不是每个类都必须要有析构函数,比如没有用new创建空间的类,一般在构造函数中有动态申请内存的,必须有析构函数。