类和动态内存分配
以下未标注的都是C++特有的功能
1. 静态存储类
如果在类中定义了一个statics int a;那么无论创建多少个对象,都只共享一个静态成员a,这在所有类对象具有相同值的私有数据,这一应用场合是十分有用的。
2. 类中C++自动提供的函数
对于以下五种函数,如果程序员不提供,编译器会自动提供。
- 默认构造函数
- 默认析构函数
- 复制构造函数
- 赋值运算符
- 地址运算符
对于编译器提供的默认析构函数,编译器不会进行任何操作。对于编译器提供的地址运算符,会返回this指针,即调用对象的地址,这与我们的初衷是一致的。下面着重对函数,3,4进行讨论。
2.1 默认构造函数
- 如果程序员不提供任何构造函数,编译器会提供一个默认构造函数,这个函数不接受任何的参数,不返回任何的参数,不执行任何的操作。比如对于类Test,那么编译器会提供:
Test::Test()
{
}
//如果在主函数有以下声明,那么a相当于一个初始值未知的类,相当于int a,这个a的值是多少并不知道。
Test a;
- 如果想要在定义的时候就初始化,那么可以显式地定义一个默认构造函数,即:
Test::Test(const int a = 1)
{
num = a; //num是类的私有数据
}
2.2 复制构造函数
复制构造函数的作用是将一个对象复制到新创建的对象,这也就意味着,这个函数是在初始化的时候调用,而不是常规的赋值过程调用。
这个函数的原型通常如下:
Class_name(const Class_name &);
在定义的时候:
Class_name::Class_name(const Class_name &);
以下的四种声明都会调用复制构造函数,他们的本质特点都是在新建一个对象的时候将其初始化为一个已有对象(这个本质特点也就意味着按值传递对象和返回对象时都会调用复制构造函数)。
StringBad Bill(Tom);
StringBad Bill = Tom;
StringBad Bill = StringBad(Tom);
StringBad *PtrToBill = new StringBad(Tom); //这个的意思是新建一个对象初始化为Tom,然后把新建对象的地址传给PtrToBill
编译器提供的隐式复制构造函数是一种浅复制,即按值复制,如果不涉及到指针和引用,就没关系,如果涉及到指针和引用,那么浅复制会让两个不同的对象指向同一地址,这在析构的时候会引发错误。
此时最好定义一个显式的复制构造函数,来避免这个灾难。就是在程序员自己定义的复制构造函数中,申请内存空间,将值复制到这个新的内存空间中,这样两个对象不会指向同一地址,但是地址的内容相同,这就是深复制。
2.3 赋值运算符
赋值运算符的作用是将一个已有对象复制到另一个已有对象,这也就意味着是常规的赋值过程调用。
这个函数的原型通常如下:
Class_name& operator= (const Class_name &);
在定义的时候
Class_name& Class_name::operator= (const Class_name &);
编译器提供的隐式赋值运算符问题也在于是一种浅复制,一旦涉及到指针或者引用的话,需要程序员自己编写一个深复制的赋值运算符,以进行重载。当然,这个实现的细节要用delete先把之前的指针指向的内存释放掉,重新给一个内存空间,然后再进行深度复制,这样做的目的是为了防止之前的空间不足,比如之前是给了10个字符的空间,但是之后赋值需要15个字符的空间,所以需要重新分配空间,防止数组访问越界。
注意运算符(=)只能用成员函数重载,不能用友元重载
3. 关于返回对象的说明
3.1 返回指向const对象的引用
例如在函数定义中:
const Test & Test::compare(const Test&a1, const Test&a2)
{
if(a1.num > a2.num)
return a1;
else
return a2;
}
显然这种方式需要这几点:
- 返回引用对象必须是传入的引用参数,因为在函数内的变量都是临时变量,一旦函数结束,内存全被释放掉了。
- 传入的参数也是const变量,当然不是的话,非const可以强制转换为const,也没关系。
显然这种引用传递,效率更高。
3.2 返回指向非const对象的引用
- 这种返回类型的典型案例是重载赋值运算符和重载输出流<<。
- 在重载赋值运算符的时候,需要return *this; 这个通常是用成员函数。return *this的目的是为了使S1 = S2 = S3这种连续赋值能够成立。
- 在重载输出流时,需要return os; 这个通常是用友元函数。
3.3 返回对象
这是在返回的是函数内的变量,即局部变量时使用。典型应用是算术运算符的重载
3.4 返回const对象
这是为了避免一些错误的输入格式。比如对于加法运算符的重载
Vector Vector::operator+(const Vector &b)
{
return Vector(x+b.x, y+b.y);
}
这样定义的话,假设有三个Vector类的对象a、b、c,那么a+b = c也不会报错,但显然应该要写为c = a+b。因为在a+b=c的语句中,a+b会先计算,然后调用复制构造函数,将结果赋给一个临时对象,接着又把对象c赋给这个临时对象,语句结束后,临时对象消失,整个过程其实什么都没做,但编译器也不会报错。
但是如果按照下面的定义:
const Vector Vector::operator+(const Vector &b)
{
return Vector(x+b.x, y+b.y);
}
那么a+b=c就会报错了,因为此时的临时变量会是一个const类型,把c赋给const类型是非法的。
4. 使用指向对象的指针
比如:
class Vector
{
...
}
int main
{
Vector *p = new Vector(2,3);
...
delete p;
}
指向对象的指针,直接用delete释放就好了,里面如果有动态内存分配,析构函数会自行处理。
5. 转换函数
- 单个值转换为类的类型,需要如下原型的类构造函数:
c_name (type_name value);
为了防止隐式的转换,可以使用关键词explicit。
2. 类的类型转换为其他类型,需要如下原型的类成员函数
operator type_name ();
注意上面这个函数虽然没有返回值,但是在函数里面需要return 对应的type_name类型