本章内容包括:
类可以有静态类成员,可以在类声明之外使用单独的语句来进行初始化,这是因为静态类成员是单独存储的,而不是对象的组成部分。
静态类成员的初始化在方法文件中,不是在类声明文件中进行的,不能再头文件中初始化,因为可能产生多个语句副本,引起错误。
对于动态分配,析构函数的delete是必不可少的。
自动存储对象被删除的顺序与创建顺序相反。
本示例中的程序,是由于编译器自动生成的成员函数引起的。
C++会自动定义一些成员函数:
隐式地址运算符返回调用对象的地址(即this指针的值)
StringBad ditto(motto); StringBad metoo = motto; StringBad also = StringBad(motto); StringBad * pStringBad = new StringBad(motto);
最后一种声明使用motto初始化一个匿名对象,并将新对象的地址赋给pStringBad指针。
程序生成了对象副本时,编译器都将调用复制构造函数。
默认的复制构造函数将逐个复制非静态成员(成员复制称为浅复制),复制的是成员的值。
如果成员本身就是类对象,则将使用这个类的复制构造函数来复制成员对象。静态成员不受影响,因为它们属于整个类,而不是对象。
复制构造函数没有复制字符串,复制了地址,执行完函数后会销毁复制对象,同时销毁被复制的字符串。
C++允许类对象赋值,这是通过自动为类重载运算符实现的,这种运算符的原型如下:
Class_name & Class_name::operator=(const Class_name &)
它接受并返回一个类对象的引用。
1.赋值运算符的功能以及何时使用它
将已有的对象赋给另一个对象时,将使用重载的赋值运算符。
初始化对象时,并不一定会使用赋值运算符:初始化总是会调用复制构造函数,使用=运算符可能会调用赋值运算符
2. 赋值的问题出在哪里
数据受损,与成员复制出现的问题相同。
3. 解决赋值的问题
解决方法是提供赋值运算符定义,其实现与复制构造函数类似,但存在差别。
通过返回一个对象,函数可以像常规赋值操作一样,进行连续赋值。
赋值操作并不创建新的对象,因此不需要调整静态数据成员的值。
String::String() { len = 0; str = new char[1]; str[0] = '\0' } 可以将: str = new char[1]; str[0] = '\0' 改成: str = 0; // set str to the null pointer or str = nullptr;
在String类中,执行比较操作的方法有三个。按字母顺序排列,第一个字符串在第二个字符串之前,则Operator<()函数返回true。
使用strcmp,第一个参数位于第二个参数之前,返回一个赋值。
使用operator来重载该运算符。对于[]运算符,一个操作数位于中括号前面,另一个操作数位于中括号之间。
可以将成员函数声明为静态的。
静态成员函数:
重载赋值运算符之前,将一个字符串赋给String对象需要以下几步:
逐成员复制将使用成员类型定义的复制构造函数和赋值运算符
成员函数或独立的函数返回对象时,可以返回对象的引用、指向对象的const引用或const对象。
可以返回对象,效率低,因为会调用复制构造函数;
可以返回const引用
重载赋值运算符以及cout<<运算符
如果被返回的对象是调用函数的局部变量,则不应按引用方式返回它,应该返回对象。
通常,被重载运算符属于这一类。
如果没有返回常对象,则force1 + force2 = net是合法的,但是该代码却不合理,因此应该返回常对象,不能在左边。
方法或函数要返回局部对象,则应返回对象。
使用new初始化对象
Class_name为类,value的类型为Type_name,则下面的语句:
Class_name * pclass = new Class_name(value);
会调用如下构造函数:
Class_name(Type_name)
这里可能还有一些琐碎的转换,如:
Class_name(const Type_name &)
下面的初始化方式将调用默认构造函数:
Class_name * pclass = new Class_name
String * favourite = new String(sayings[choice])使用new来为整个对象分配内存:这里是为保存字符串地址的指针和len成员分配内存。
delete favourite,不会释放str指向的内存,该任务由析构函数来完成。
析构函数被调用的时机:
使用对象指针,需要注意:
new的过程:先分配空间,然后将地址赋给指针变量。
使用定位new运算符:
重新定义<<运算符,cout一起用来显示对象的内容:
std::ostrem & operator<<(ostream & os,const c_name & obj) { os << ...; return os; }
要将单个值转转换为类类型,需要创建原型如下所示的类构造函数:
c_name(type_name value);
其中c_name为类名,type_name是要转换的类型名称。
要将类转换为其他类型,需创建原型如下的类成员函数:
operator type_name();
虽然该函数没有声明返回类型,但应返回所需类型的值。
声明构造函数时,使用explicit,可防止它被用于隐式转换。
队列时一种抽象的数据类型,(Abstract Data Type, ADT),可以存储有序的项目序列。新项目被添加在队委,并可以删除队首的项目。
需要设计一个队列类,队列的特征如下:
类队列需要有的数据成员,队首、队尾指针,队列最大成员数,队列成员数。
通常将节点的声明放在类中,但只能在类中使用。
嵌套结构和类
在类声明中声明的结构、类或枚举被称为是嵌套在类中,其作用域为整个类。
3.类方法
对于类的私有常变量数据成员,其初始化应该使用列表初始化。成员初始化列表的方法并不限于初始化向量。
对于被声明为引用的类成员,必须使用初始化列表语法。
列表初始化的注意事项:
成员初始化列表使用的()方式也可以用于常规初始化。如int games(162);与int games = 162;等价。C++11的类内初始化
class Classy { int mem1 = 10; const int mem2 = 20; // ... }
入队方法需要经过几个阶段:
1.如果队列已满,则结束
2.创建一个新节点。如果new无法创建新节点,它将引发异常
3.在节点中放入正确的值
4.将项目计数加1
5.将节点附加到队尾。首先,将节点与队尾节点连接起来,将rear指向新的队尾,如果队列为空,将front指向新节点。
出队需要多个步骤:
1.队列为空就结束。
2.将队列的第一个项目提供给调用函数,item=ftonr->item;
3.将项目计数减1
4.保存front节点的位置
5.让节点出队。front = front->next;
6.删除出队的节点
7.如果链表为空,则将rear设置为NULL,将front设置为NULL
其他类方法:
1.加入String类有如下私有成员:
class String { private: char * str; int len; // ... }
a. 下述默认构造函数有什么问题?
String::String(){}
对于使用指针作为私有成员的类,其默认构造函数必须将成员初始化。
b. 下述构造函数有什么问题?
String::String(const char * s)
{
str = s;
len = strlen(s)
}
str只是和字符串的地址相等,没有创建新的字符串。
c. 下述构造函数有什么问题?
String::String(const char * s)
{
strcpy(str,s);
len = strlen(s)
}
没有为str分配内存空间,无法复制成功。
2.如果你定义了一个类,其指针成员是使用new初始化的,请指出可能出现的3个问题以及如何纠正这些问题。
class nifty { // data char personality[]; int talents; // methods nifty(); nifty(char * s); ostream & operator<<(ostream & os, nifty & n); } nifty:nifty() { personality = NULL; talents = 0; } nifty:nifty(char * s) { personality = new char [strlen(s)]; personality = s; talents = 0; } ostream & operator<<(ostream & os, const nifty & n) { os << n; }
方法没有声明公有,形参没有使用const,重载运算符没有使用友元,自定义构造函数分配空间的长度不对,运算符重载实现方法不对,且没有返回值。修改如下:
class nifty { // data char personality[]; int talents; // methods publice: nifty(); nifty(const char * s); friend ostream & operator<<(ostream & os, nifty & n); } nifty:nifty() { personality[0] = '\0'; talents = 0; } nifty:nifty(char * s) { personality = new char [strlen(s)+1]; strcpy(personality, s); talents = 0; } ostream & operator<<(ostream & os, const nifty & n) { os << n.personality << ": " << n.talents; return os; }
5.对于下面的类声明:
class Golfer
{
private:
char * fullname;
int games;
int * scores;
public:
Golfer();
Golfer(const char * name, int g = 0);
// creates empty dynamic array of g elements if g > 0
Golfer(const Golfer & g);
~Golfer();
};
a. 下列各条语句将调用哪些类方法?
Golfer nancy; // #1 Golfer lulu("Little lulu"); // #2 Golfer roy("Roy Hobbs", 12); // #3 Golfer * par = new Golfer; // #4 Golfer next = lulu; // #5 Golfer hazzard = "Weed Thwacker"; // #6 *par = nancy; // #7 nancy = "Nancy Putter"; // #8 1调用默认构造函数,2调用自定义构造函数,3调用自定义构造函数,4默认构造函数,5调用复制构造函数,和默认赋值函数,6调用构造函数转换函数 7调用默认赋值函数 8调用构造转换函数**默认赋值运算符**。 b. 很明显,类需要另外几个方法才能更有用,但是类需要哪些方法才能防止数据被破坏呢? ~~深度复制复制构造函数~~,深度复制赋值函数。</font>