C++中既然有结构体了,并且在C语言基础上对结构体进行了优化,可以给结构体里面添加方法了,也可以设置访问权限了,那么为何还需要一个类呢?
其实首当其冲的原因就是为了安全性,结构体默认访问类型是public的,而类的默认访问类型是private的。
虽然在使用上类和结构体大同小异,但是也方便了不少,并且还是有一些需要注意的地方。
首先,使用结构体时往往会要typedef xxx,否则用起来名称很长很麻烦,类就直接省略这一步了,当然这其实也没什么大不了的。
好吧,先看一下类的定义形式。
class 类名
{
public:
公有成员;
protected:
保护成员;
private:
私有成员;
}
这个访问类型和结构体是一样的,只提一下protected,表示能被自己或子类访问。这个和java中的protected是有区别的,java的protected表示能被该类所在的包以及子类访问。
访问类型出现的次数、顺序没有任何规定,想出现几次就出现几次。通常将数据成员都设置为private,然后设置一些公有的setter和getter,这和java思想是一致的。
需要强调的一个地方是,虽然说这个数据成员可以是任意类型的,但是绝对不能是自身类的对象,但可以是自身类的指针或引用。比如下面的例子:
class A
{
private:
A a; //错误
A *a1, &a2; //正确
}
同时这个数据类型也不能是auto、register、extern,不过这几种类型我们一般也不会用到就是了。
还有一点是和java等语言有很大的不同的地方就是,在声明(或定义)类的时候,不能为数据成员赋初值!!!
下面讲成员函数:
成员函数有两种声明方式,一种是在类声明中就定义,另一种是在类外定义。
在类声明中定义的形式和普通函数的定义格式相同,就不啰嗦了,下面放一下在类外定义函数的格式:
返回类型 类名::成员函数名(参数表)
{
...
}
一般来说,在C++中还是习惯于将类的定义与函数的定义分开来写,以免一个类的代码块显得过大,影响阅读。
有一个比较奇葩一点的成员函数就是常量成员函数,这种声明后的函数不能对类的成员变量进行修改。注意只有类的成员函数才能成为常量成员函数,它的声明方式如下:
class A
{
private:
int a;
public:
void getA() const; // 在类的声明后加上一个const
}
构造函数和析构函数
这俩函数都是由系统自动执行的,在程序中不可以显式调用。
构造函数用于初始化(C++正好不允许在定义变量时给变量赋初值,那就在构造函数里给咯),析构函数就是在对象生命周期结束的时候用于回收对象。
构造函数:
- 与类同名
- 没有返回类型,void也不行
- 可被重载
- 由系统自动调用,不允许显式调用
- 常定义为public,因为需要在生成新对象时被编译系统隐式调用,不过有时候也可以将其定义为private,这种情况往往是通过重载用其他构造函数来调用这个构造函数,不过至少要有一个公有构造函数
- 如果没有定义构造函数,系统会自动生成一个无参的空的构造函数,但是如果给定了一个有参的构造函数,那么系统就不会自动生成无参构造函数了。
- 定义对象数组的时候必须要有无参的构造函数
形式:
class A
{
...
A(...);
}
拷贝构造函数
形如:X::X(const X&){}
的构造函数,如果没有显式定义的话也是会自动生成的。其功能就是他的名字,通过拷贝其他对象的值来生成一个对象。
自动生成的这个拷贝构造函数是位复制的,即如果类里有指针类型的数据成员的话就会产生指针悬挂问题。因此这种自动生成的拷贝构造函数往往称之为浅拷贝,而如果需要对于指针类型数据成员也进行拷贝的话,就要自己写代码实现了,这种就称之为深拷贝。
拷贝构造函数的使用方法是:
class A{};
A a1;
A a2 = a1; // 方法一
A a3(a1); // 方法二
f(A t); // 方法三:这是将对象作为函数的参数传入的时候
加入该类中存在指针成员,则当p2调用了delete后,p1的指针将指向p2被delete的存储区域,这就是指针悬挂。
拷贝构造函数的说明:
- 拷贝构造函数与一般构造函数相同,与类同名,没有返回类型,可以重载。
- 拷贝构造函数的参数常常是const类型的本类对象的引用。
- 在多数情况下,默认拷贝构造函数能够完成对象的复制创建工作,但当类具有指针类型的数据成员时,默认拷贝构造函数就可能产生指针悬挂问题,需要提供显式的拷贝构造函数。
- 对拷贝构造函数的调用常在类的外部进行,应该将它指定为类的公有成员。
构造函数与初始化列表
形如:
构造函数名(参数表): 成员1(初始值),成员2(初始值)...
{
...
}
这种写法就是省去了在构造函数里对成员变量一个个的初始化的麻烦,功能是一样的,只是方便了一点。
而初始化列表的初始化功能执行顺序不是按照初始化列表的排列顺序,而是按照这个成员变量在类里的排列顺序来执行的。
且初始化列表的执行先于构造函数体内部代码的执行。
注意:
常量成员、引用成员、类对象成员、派生类构造函数对基类构造函数的调用必须使用初始化列表来进行初始化。
析构函数
形如:
class A
{
~A(){...}
}
要求:
- 函数名为~加类名
- 没有参数
- 没有返回值
- 不能重载,每个类只有一个析构函数
也是自动调用的,调用时机如下:
- 对象生命周期结束时
- 自动/局部对象:定义的语句块结束处
- 静态对象/全局对象:程序结束时
一般情况下用缺省的析构函数就行了,除非这个对象动态申请了一些内存空间,这个时候就需要手动释放了。
静态函数
静态函数只能访问静态成员和静态函数,普通函数可以访问静态成员和静态函数。
在类外定义静态成员函数时不能加static。
成员函数中有个this指针是指向自己的,注意是指针,所以是用 ->
来指的。
友元
友元函数
一个类的友元函数能直接访问该类的所有成员,private的都可以!
定义形式如下:
class A
{
...
friend T f(...); // 声明f为A类的友元函数
}
T f(...) { ... }
其实从上面代码也看得出,友元函数不是成员函数,定义时不能使用“A::f”这样的限定。
友元类
一个类也可以是另一个类的友元,友元类的所有成员函数都是另一个类的友元函数,能够直接访问另一个类的所有成员。
声明如下:
class A
{
public:
friend class B; //声明类B是类A的友元类
}
友元成员函数
指定另一个类的某个成员函数为该类的友元函数。
class A;
class B
{
public:
void BBB(A a); // 该函数未类A的友元函数
};
class A
{
public:
friend void B::BBB(A a); //声明友元函数
};