类和动态内存分配-进阶
(1) 改进后的新String类
新String类将添加以下方法:
int length () const {return len;}
friend bool operator<(const String & st, const String & st2);
friend bool operator>( const String & st1, const String & st2);
friend bool operator==( const String & st, const String & st2);
friend operator>>(istream & is,String & st);
char & operator [] (int i);
const char & operator [] (int i) const;
static int HowMany();
第一个新方法返回被存储的字符串的长度。 接下来的3个友元函数能够对字符串进行比较。 operator>>()函数提供了简单的输入功能;两个operator[]()函数提供了以数组表示法访问字符串中各个字符的功能。 静态类方法HowMany()将补充静态类数据成员num_string。
- 修订后的默认构造函数:
请注意新的默认构造函数,它与下面类似:
String::String()
{
len=0;
str=new char[1];
str[0]=’\0’; //默认string
}
您可能会问,为什么代码为:
str = new char[1];
而不是:
str = new char;
上面两种方式分配的内存量相同,区别在于前者与类析构函数兼容,而后者不兼容。
2. 比较成员函数
在String类中,执行比较操作的方法有3个。 如果按字母顺序,第一个字符串在第二个字符串之前,则operator<()函数返回true。 要实现字符串比较函数,最简单的方法是使用标准的strcmp()函数,如果依照字母顺序,第一个参数位于第二个参数之前,则该函数返回一个负值;如果两个字符串相同,则返回0;如果第一个参数位于第二个参数之后,则返回一个正值。 因此,可以这样使用strcmp():
bool operator<(const String &st1, const String &st2)
{
if (std::strcmp(st1.str,st2.str)<0)
return true;
else
return false;
}
因为内置的>运算符返回的是一个布尔值,所以可以将代码进一步简化为:
bool operator<(const String &st1, const String &st2)
{
return (std::strcmp(st1.str,st2.str)<0);
}
同样,可以按照下面的方式来编写另外两个比较函数:
bool operator>(const String &st1, const String &st2)
{
return st2<st1;
}
bool operator==(const String &st1,const String &st2)
{
return (std::strcmp(st1.str,st2.str)==0);
}
将比较函数作为友元,有助于将String对象与常规的C字符串进行比较。 例如,假设answer是String对象,则下面的代码:
if(“love”==answer)
将被转换为:
if(operator==(“love”,answer))
然后,编译器将使用某个构造函数将代码转换为:
if(operator==(String(“love”),answer))
这与原型是相匹配的。
3. 使用中括号表示法访问字符
对于标准C-风格字符串来说,可以使用中括号来访问其中的字符:
char city[40] = “Amsterdam”;
cout<<city[0]<<endl; //显式字母A
在C++中,两个中括号组成一个运算符——中括号运算符,可以使用方法operator[]()来重载该运算符。 通常,二元C++运算符(带两个操作数)位于两个操作数之间,例如2+5。 但对于中括号运算符,一个操作数位于第一个中括号的前面,另一个操作数位于两个中括号之间。 因此,在表达式city[0]中,city是第一个操作数,[]是运算符,0是第二个操作数。
假设opera是一个String对象:
String opera(“The Magic Flute”);
则对于表达式opera[4],C++将查找名称和特征标与此相同的方法:
String::operator[](int i)
如果找到匹配的原型,编译器将使用下面的函数调用来替代表达式opera[4]:
opera.operator[](4)
opera对象调用该方法,数组下标4成为该函数的参数。
下面是该方法的简单实现:
char & String::operator[](int i)
{
return str[i];
}
有了上述定义后,语句:
cout<<opera[4];
将被转换为:
cout<<opera.operator[4];
返回值是opera.str[4](字符M)。 由此,公有方法可以访问私有数据。
将返回类型声明为char &,便可以给特定的元素赋值。 例如,可以编写这样的代码:
String means(“might”);
means[0]=’r’;
第二条语句将被转换为一个重载运算符函数调用:
means.operator[][0]=‘r’;
这里将r赋给方法的返回值,而函数返回的是指向means.str[0]的引用,因此上述代码等同于下面的代码:
means.str[0]=’r’;
代码的最后一行访问的是私有数据,但由于operator[]()是类的一个方法,因此能够修改数组的内容。 最终的结果是”might”被改为”right”。
假设有下面的常量对象:
const String answer(“futile”);
如果只有上述operator[]()定义,则下面的代码将出错:
cout<<answer[1];
原因是answer是常量,而上述方法无法确保不修改数据。
但在重载时,C++将区分常量和非常量函数的特征标,因此可以提供另一个仅供const String对象使用的operator[]()版本:
//针对const String对象
const char & String::operator[](int i) const
{
return str[i];
}
有了上述定义后,就可以读/写常规String对象了;而对于const String对象,则只能读取其数据:
String text(“Once upon a time”);
const String answer(“futile”);
cout<<text[1]; //使用非常量版本的operator []()
cout<<answer[1]; //使用常量版本的operator[]()
cin>>text[1]; //使用非常量版本的operator[]()
cin>>answer[1]; //报错
4. 静态类成员函数
可以将成员函数声明为静态的,这样做有两个重要的后果。
首先,不能通过对象调用静态成员函数;实际上,静态成员函数甚至不能使用this指针。 如果静态成员函数是在公有部分声明的,则可以使用类名和作用域解析运算符来调用它。 例如,可以给String类添加一个名为HowMany()的静态成员函数,方法是在类声明中添加如下原型/定义:
static int HowMany() {return num_strings;}
调用它的方式如下:
int count = String::HowMany(); //调用静态成员函数
其次,由于静态成员函数不与特定的对象相关联,因此只能使用静态数据成员。 例如,静态方法HowMany()可以访问静态成员num_strings,但不能访问str和len。
5. 进一步重新赋值运算符
假设要将常规字符串复制到String对象中。 例如,假设使用getline()读取了一个字符串,并要将这个字符串放置到String对象中,前面定义的类方法让您能够这样编写代码:
String name;
char temp[40];
cin.getline(temp,40);
name = temp; //使用构造函数来转换类型
最后一条语句工作原理:
- 程序使用构造函数String(const char *)来创建一个临时String对象,其中包含temp中的字符串副本。
- String & String::operator=(const String &)函数将临时对象中的信息复制到name对象中。
- 程序调用析构函数~String()删除临时对象。
为提高处理效率,最简单的方法是重载复制运算符,使之能够直接使用常规字符串,这样就不用创建和删除临时对象了。 下面是一种可能的实现:
String & String::operator=(const char * s)
{
delete [] str;
len=std::strlen(s);
str=new char[len+1];
std::strcpy(str,s);
return *this;
}
一般来说,必须释放str指向的内存,并未新字符串分配足够的内存。
6. 程序清单
//string1.h -- 修正并加强后的String类定义
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#ifndef STRING1_H_
#define STRING1_H_
using std::ostream;
using std::istream;
class String
{
private:
char * str; //指向字符串的指针
int len; //字符串的长度
static int num_strings; //对象个数
static const int CINLIM = 80; //cin输入上限
public:
//构造函数和其他方法
String(const char * s); //构造函数
String(); //默认构造函数
String(const String &); //复制构造函数
~String(); //析构函数
int length() const { return len; }
//重载运算符方法
String & operator=(const String &);
String & operator=(const char *);
char * operator[](int i);
const char & operator[](int i) const;
//重载运算符友元
friend bool operator<(const String & st, const String & st2);
friend bool operator>(const String & st1, const String & st2);
friend bool operator==(const String & st, const String & st2);
friend ostream & operator<<(ostream & os, const String & st);
friend istream & operator<<(istream & is, String & st);
//静态函数
static int HowMany();
};
#endif // !STRING1_H_
//string1.cpp -- String类方法
#define _CRT_SECURE_NO_WARNINGS
#include<cstring>
#include"string1.h"
using std::cin;
using std::cout;
//初始化静态类成员
int String::num_strings = 0;
//静态方法
int String::HowMany()
{
return num_strings;
}
//类方法
//以C字符型构造String
String::String(const char * s)
{
len = std::strlen(s); //设置长度
str = new char[len + 1]; //分配内存
std::strcpy(str, s); //初始化指针
num_strings++; //设置对象数量
}
String::String() //默认构造函数
{
len = 4;
str = new char[4];
str[0] = '\0'; //默认字符串
num_strings++;
}
String::String(const String &st)
{
num_strings++; //处理静态成员更新
len = st.len; //相同长度
str = new char[len + 1]; //分配空间
std::strcpy(str, st.str); //将字符串复制到新的位置
}
String::~String() //必要的析构函数
{
--num_strings; //有要求
delete[] str; //有要求
}
//重载运算符方法
//将一个String赋给另一个String
String & String::operator=(const String & st)
{
if (this == &st)
return *this;
delete[] str;
len = st.len;
str = new char[len + 1];
std::strcpy(str, st.str);
return *this;
}
//将一个C String赋给另一个String
String & String::operator=(const char *s)
{
delete[] str;
len = std::strlen(s);
str = new char[len + 1];
std::strcpy(str, s);
return *this;
}
//对于非常量String有读写权限
char & String::operator[](int i)
{
return str[i];
}
//对于常量String只读权限
const char & String::operator[](int i) const
{
return str[i];
}
//重载运算符友元
bool operator<(const String &st1, const String &st2)
{
return (std::strcmp(st1.str, st2.str) < 0);
}
bool operator>(const String &st1, const String &st2)
{
return st2 < st1;
}
bool operator==(const String &st1, const String &st2)
{
return(std::strcmp(st1.str, st2.str) == 0);
}
//简单的String输出
ostream & operator<<(ostream & os, const String & st)
{
os << st.str;
return os;
}
//快速输入
istream & operator>>(istream & is, String & st)
{
char temp[String::CINLIM];
is.get(temp, String::CINLIM);
if (is)
st = temp;
while (is&&is.get() != '\n')
continue;
return is;
}
//sayings1.cpp -- 使用拓展的String类
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include"string1.h"
const int ArSize = 10;
const int MaxLen = 81;
int main()
{
using std::cout;
using std::cin;
using std::endl;
String name;
cout << "Hi, what's your name?\n";
cin >> name;
cout << name << ", please enter up to " << ArSize
<< " short sayings <empty line to quit>:\n";
String sayings[ArSize]; //对象数组
char temp[MaxLen]; 临时字符串存储
int i;
for (i = 0; i < ArSize; i++)
{
cout << i + 1 << ": ";
cin.get(temp, MaxLen);
while (cin&&cin.get() != '\n')
continue;
if (!cin || temp[0] == '\0') //空行?
break; //i不自增
else
sayings[i] = temp; //重载赋值
}
int total = i; //读到行数总和
if (total > 0)
{
cout << "Here are your sayings:\n";
for (i = 0; i < total; i++)
cout << sayings[i][0] << ": " << sayings[i] << endl;
int shortest = 0;
int first = 0;
for (i = 1; i < total; i++)
{
if (sayings[i].length() < sayings[shortest].length())
shortest = i;
if (sayings[i] < sayings[first])
first = i;
}
cout << "Shortest saying:\n" << sayings[shortest] << endl;
cout << "First alphabetically:\n" << sayings[first] << endl;
cout << "This program used " << String::HowMany()
<< " String objects. Bye.\n";
}
else
cout << "No input! Bye.\n";
cin.get();
return 0;
}
该程序中,程序要求用户输入至多10条谚语。 每条谚语都被读到一个临时字符数组,然后被复制到String对象中。 显式用户的输入后,程序使用成员函数length()和operator<()来确定最短的字符串以及按字母顺序排列在最前面的字符串。