(1)静态类成员
class stringBad
{
private:
char * str;
int len;
static int num_strings;//静态成员变量
public:
StringBad(const char * s);
};
//类外初始化静态变量
int StringBad::num_strings = 0;
特点:
1、无论创建了多少个对象,程序都只创建一个静态类变量副本。即所有对象共享同一个静态成员。
2、不能在类声明中初始化静态成员变量,这是因为声明描述了如何分配内存,但并不分配内存。
3、可以在类声明之外使用单独的语句进行初始化,因为静态类成员是单独存储的,而不是对象的组成部分。
4、初始化时不适用关键字static
5、静态数据成员在类声明中声明,在包含类方法的文件中进行初始化。初始化时使用作用域运算符来指出静态成员所属类。
6、如果静态成员时整形或枚举型const,则可以在类声明中初始化
(2)动态内存分配
StringBad::StringBad(const char * s)
{
len = std::strlen(s);
str = new char[len+1];
std::strcpy(str,s);
num_strings++;
}
StringBad::~StringBad()
{
--num_strings;
delete []str;
}
1、构造函数必须分配足够的内存来存储字符串,其中字符串并不保存在对象中,字符串单独保存在堆内存中,对象仅保存了指向该字符串的指针。
2、在构造函数使用new来分配内存时,必须在相应的析构函数里面调用delete,释放申请的内存。若使用new[],则应使用delete[]来释放内存。
(3)复制构造函数
当使用一个对象来初始化另一个对象时,编译器将自动调用复制构造函数
1、复制构造函数用于将一个对象复制到新创建的对象中。用于初始化过程中(包括按值传递),而不是常规的赋值过程。
2、当程序生成了对象副本时,编译器都将使用复制构造函数:当函数按值传递对象(创建原始变量的一个副本)、函数按值返回对象、生成一个临时对象
3、新创建一个对象并将其初始化为同类现有对象时,复制构造函数都将被调用,最常见的情况是将新对象显式地初始化为现有的对象
StringBad ditto(motto);
StringBad metoo = motto;
StringBad also = StringBad(motto);
StringBad * pStringBad = new StringBad(motto);//使用motto初始化一个匿名对象,并将新对象的地址赋给pStringBad指针
4、默认的复制构造函数逐个复制非静态成员(成员复制也成为浅复制),复制的是成员的值。若试图释放内存两次可能导致程序异常终止。
5、解决4中问题,可以定义一个显示的复制构造函数,即深度复制。复制构造函数应当复制字符串并将副本的地址赋给str成员,而不仅仅是复制字符串地址。这样每个对象都有自己的字符串,而不是引用另一个对象的字符串。调用析构函数时都将释放不同的字符串,而不会试图区释放已经被释放的字符串。
StringBad::StringBad(const StringBad &st)
{
num_strings++;
len = str.len;
str = new char[len+1];
std::strcpy(str,st.str);
}
6、必须定义复制构造函数的原因在于,一些类成员是使用new初始化的、指向数据的指针,而不是数据本身。
(4)赋值运算符
C允许结构赋值,C++允许类对象赋值,这是通过自动为类重载赋值运算符实现的。
赋值运算符的原型为:
Class_name & Class_name::operator=(const Class_name &);
1、将已有对象赋值给另一个对象时,将使用重载的赋值运算符
StringBad & StringBad::operator=(const StringBad & );
StringBad headline1("123");
StringBad knot;
knot = headline1;
2、初始化对象时,并不一定会使用赋值运算符
StringBad metoo = knot;
metoo是一个新创建的对象,被初始化为knot的值,因此使用复制构造函数。实现时也可能分两步来处理这条语句:使用复制构造函数创建一个临时对象,然后通过赋值运算符将临时对象的值复制到新对象中。
3、赋值的问题
上述1中将headline1赋给knot,为knot调用析构函数后,再为headline1调用析构函数,会释放同一块内存,造成程序崩溃。
4、解决赋值的问题
提供赋值运算符定义,其实现与复制构造函数相似。
StringBad & StringBad::operator=(const StringBad & st)
{
if (this == st)
return *this;
delete [] str;
len = str.len;
str = new char[len + 1];
std::strcpy(str, st.str);
return *this;
}
(5)静态类成员函数
将成员函数声明为静态类成员函数,函数声明必须包含关键字static,但如果函数定义是独立的,则其中不能包含关键字static。
1、静态成员函数不能通过对象调用静态成员函数。静态成员函数甚至不能使用this指针。如果静态成员函数是在公有部分声明的,则可以使用类名和作用域解析运算符来调用它。
static int HowMany(){return num_strings;}
int count = StringBad::HowMany();//调用方式
2、静态成员函数不与特定的对象相关联,因此只能使用静态数据成员。例如静态方法HowMany()可以访问静态成员num_strings,但不能访问str和len
3、可以使用静态成员函数设计类级的标记(数据),以控制某些类接口的行为
当成员函数或独立的函数返回对象时,有几种返回方式可供选择:指向对象的引用、指向对象的const引用、const对象
1、返回指向const对象的引用
const Vector & Max(const Vector & v1, const Vector & v2)
{
if (v1.magval() > v2.magval())
return v1;
else
return v2;
}
2、返回指向非const对象的引用
两种常见的返回非const对象的情形是,重载赋值运算符以及重载与cout一起使用的<<运算符,前者旨在提高效率,后者必须这样做。
3、返回对象
如果返回的对象是被调用函数中的局部变量,则不应按引用方式返回它,因为在被调用函数执行完毕时,局部对象将调用其析构函数,通常被重载的算术运算符属于这一类
总之,如果方法或函数要返回局部对象,则应返回对象,而不是指向对象的引用。在这种情况下,将使用复制构造函数来生成返回的对象。如果方法或函数要返回一个没有公有复制构造函数的类的对象,它必须返回一个一个指向这种对象的引用。最后,有些方法和函数可以返回对象或指向对象的引用,则首选引用,因为其效率更高
1、在类声明中声明为结构、类或枚举被称为时被嵌套在类中,其作用域为整个类。如果声明是在类的私有部分,则只能在这个类使用这些成员;如果声明是在公有部分进行,则可以在类外通过作用域解析运算符使用声明的成员变量
2、如果类成员为const类型,则只能对此种类型的值进行初始化,不能进行赋值。从概念上说,调用构造函数时,对象将在括号中的代码指向之前被创建,因此,调用构造函数将为成员变量分配内存。然后,程序流程执行到构造函数体,使用常规的赋值方式将值存储到内存中。因此,必须在执行到构造函数之前,即创建对象时初始化const变量。C++提供了初始化列表来完成此种赋值操作。
3、只有构造函数可以使用这种初始化列表语法。对于const类成员和被声明为引用的类成员,必须使用初始化列表。