有比较函数是strcmp (参数1, 参数2)
参数是两个字符串,所在头文件是<string>
比较方法是按顺序依次比较参数1和参数2的第一个字符(看ASCII值)。
假如相同,则比较下一个;
假如参数1的比参数2的大,则返回1(正数);
假如参数1的比参数2的小,则返回-1(负数);
假如两个字符串完全一样,则返回0。
其原理是(这个我自己写的):
int strcmp(const char* a, const char* b) { while (*a == *b) { if (*a == *b == 0)return 0; a++, b++; } if (a > b) return 1; else if (a < b) return -1; }
注意:
①具体实现可能有所区别,但都是逐个判断;
②strcmp函数里的指针移动,不影响函数外的指针(因为这里是按值传递)。
可以通过比较函数,编写类的比较成员函数,或者是友元函数。
作用是,比较一个字符串和一个类对象(事实上是这个类对象的某个同类成员)的关系。
例如,代码:
bool operator<(char* word) { if (strcmp(name, word) < 0)return true; //这里不能使用string类。注:string类的比较可以直接用>或者<或者== else return false; } bool operator>(char*word) { return word < name; //word<name和name>word是一个意思。假如前者为真,则后者为真,返回true } bool operator==(char*word) { return (strcmp(name, word) == 0); //如果等于0,则说明相等,返回true,否则false }
注意:
①strcmp的两个参数,是char*类型,因此不能使用string类型进行比较;
②string类型的比较,可以直接使用 >、<、= 比较2个string类字符串的大小(返回true或者false,效果同这种方法);
③为了方便,有必要加入友元函数进行比较,方法类似,如:
bool operator<(const char*word, Player& m)
{
return (m>word);
}
用中括号表示法访问字符:
中括号表示法其实就是“[]”这个符号。
在之前,假如有一个指针指向一个字符串char*a="abcd"; 那么a[0]=a;
char *a = new char[5];
strcpy_s(a, 5,"abcd");
a[0] = 'b';
cout << a << endl;
而这样一段代码中,a[0]可以把第一个字符改为字符'b',
同样,可以用运算符重载的形式,将这种方法用作类(注意,中括号只能作为成员函数重载)。
如代码:
#include<iostream> #include<string> using namespace std; class Man { string fname; string lname; public: Man() { fname = "aaa";lname = "bbb"; } //为了方便,使用默认构造函数 char&operator[](int m); void show(); }; int main() { Man a; cout << a[0] << endl; //a[0]是对象a的私有成员lname的第一个字母 a[0] = 'c'; a.show(); system("pause"); return 0; } void Man::show() { cout << fname << ", " << lname << endl; } char&Man::operator[](int m) { return lname[m]; }
显示:
b aaa, cbb 请按任意键继续. . .
注意:
①a[0]中,a表示的是调用[]重载运算符的对象,0是参数。(可以参照指针指向字符串来理解)。
②因为返回的是引用char&,因此,这种方法可以修改私有成员的数据(a[0] = 'c';);
③假如涉及到对象数组,例如Man b[3],那么b[0]表示的则不是对象的第一个字符,而是表示的是b[0]这个对象。如果需要表示第一个字符,则使用b[0][0]。
④具体返回什么,可以根据需要自行定义。这里返回的是私有成员lname的某个字符(根据参数决定)。
假如这里面对是被const关键字所限定的对象(例如返回的、或者调用的对象是const所限定的const Man a;),那么则需要修改函数定义,具体需要看情况。
①假如是成员被const所限定,那么,首先肯定不能直接返回引用(因为引用涉及修改),可以返回被const所限定的引用、或者是返回副本(即比如说返回char类型)。
如:const string lname="bbb";
则函数定义修改为:
const char& Man::operator[](int m)
{
return lname[m];
}
这里的const表示返回值被const所限定,因此不能成为左值。
②假如是类对象被const所限定,如:const Man a;,这里表示对象的成员不能被修改。那么:
(1)需要有符合其的重载定义(成员函数在括号后加const表示限定成员不能被修改);
(2)而返回值不能直接返回引用(因为这样可能导致会被修改),应返回普通类型或者是被const所限定的引用。
如:
const char& Man::operator[](int m)const
{
return lname[m];
}
第一个const表示返回值被限定,第二个const表示成员对象被限定。
③因此,如果要为被const限定的对象和不被const限定的对象(后者方便修改,且前提是返回值的成员没有被const限定)我们可以同时准备两个中括号运算符重载的定义,当遇见被const限定的对象时,则匹配const版的,否则匹配非const版的。
char& Man::operator[](int m)
{
return lname[m];
}
const char& Man::operator[](int m)const
{
return lname[m];
}
④另外,如果无需修改的话,可以直接使用const版的(放弃无const版),未被const限定的对象,也可以匹配const版的函数使用。
静态成员函数:
静态成员函数有点类似类的静态数据成员。
首先,静态成员函数的函数声明,必须包含关键字static;(但如果函数定义是独立的——指不属于任何一个类,而静态成员函数必须属于一个类,则不能使用关键字static)
其次,静态成员函数,在类外定义的话,无需再次加入static表示其是静态函数,只需要加上作用域运算符即可。例如类Man的静态成员函数static int show();在定义的时候,函数原型是int Man::show()这样
第三,不能通过 对象名.函数名 这样调用静态成员函数(因为他不属于某一个对象,属于整个类),也因此,无法使用this指针(因为this指针指向的是当前对象)。静态成员函数的调用方法是: 类名::静态成员函数名 ;
最后,静态成员函数,只能访问同类里的静态成员(因为是他不属于对象,所以不能访问属于对象的非静态成员)。
这也就是为什么静态私有成员可以在函数外声明,而不能在类的非成员函数和非友元函数中访问。是因为静态成员属于整个类(而类定义不分配内存)。
而静态成员函数,可以用于设置类级(classwide)标记,以控制某些接口的行为。例如,类级标记可以控制显示类内容的方法所用的格式。
比如说在某种情况下,调用一次静态成员函数(可以用 类型::静态成员函数名 这样的方式),然后把静态数据成员+1,于是,某些成员函数以静态数据成员为判断条件的,则可以改变执行的代码)。
另外,静态私有数据成员之所以要在类定义和函数定义之外声明,两个原因:
①非成员、友元函数内部不能访问类的私有成员;
②成员函数、友元函数在能使用的时候,已经创建了类对象了,而多个类对象共享一个静态数据成员,因此需要先有静态数据成员,后有类对象才可以;(但若全局声明一个类对象,再初始化静态数据成员似乎也没有影响,不过这种对象在静态内存区域,好吧,我也搞不懂)
③不能和类在同一个头文件声明。是因为头文件可以多次重复引用,会导致多重声明。
而#ifndef #endif 和#pragma once的作用,是防止在同一个文件内多次引用头文件,但对于多个文件引用同一个头文件,并没有作用。
赋值运算符的再次重载:
假如有构造函数:Man(const char* a);
那么若使用Man a = "abc";
编译器则会调用构造函数,创建一个临时对象("abc"作为参数),然后使用复制构造函数,将临时对象的赋值给对象a,再然后调用析构函数,删除这个临时对象。
如代码:
#include<iostream> #include<string> using namespace std; class Man { string fname; string lname; static int a; public: Man() { fname = "aaa";lname = ""; } //为了方便,使用默认构造函数 Man(const char* m); void show(); ~Man() { std::cout << "1" << std::endl; } }; int main() { Man a; a = "aaa"; system("pause"); return 0; } void Man::show() { cout << fname << ", " << lname << endl; } Man::Man(const char* m) { lname = m; }
显示:
1 请按任意键继续. . .
这里是1,就表示析构函数被调用了,否则无显示。
当类对象比较小的时候(成员很少),可能没什么大的影响。但若类成员比较多(比如有100个),那么就会影响程序效率(因为会调用构造函数生成临时对象,还要再调用析构函数删除这个对象)。
因此,可以考虑重载赋值运算符,参数为const char*
新的运算符重载函数定义为:
int Man::operator=(const char*m)
{
lname = m;
return 1;
}
此时,重新运行程序,这次便无任何显示了。注意,假如是char*指针,则不能直接用lname=m这种形式,而是应通过delete 和new来进行。
另外,之所以需要有返回值,是因为赋值运算符的表达式本身就有值,例如:
cout<< (a=0) <<endl; 便会显示0,而 cout<< (a=100) <<endl; 则会显示100。而cout << (a = 'b') << endl;则显示的为字符b。
也就是说,其输入什么,在原本的赋值运算符中,其返回值便是什么。
而这里我们自定义的赋值运算符重载,返回值可以由我们自己决定。
以上便是类定义的优化,现我根据书上更改后的类声明,自行编写类定义,然后用书上给的程序进行测试。
#ifndef STRING1_H_
#define STRING1_H_
#include<iostream>
using std::ostream;
using std::istream;
class String
{
private:
char * std;
int len;
static int num_strings;
static const int CINLIM = 80;
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);
statici nt HowMany();
};
#endif
以上是类定义,
现将完成程序如下:
//1.h 头文件,类定义 #ifndef STRING1_H_ #define STRING1_H_ #include<iostream> using std::ostream; using std::istream; class String { private: char * std; int len; static int num_strings; static const int CINLIM = 80; //输入限制 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 //2.cpp 类的成员函数定义 #include<iostream> #include"1.h" String::String(const char * s) //构造函数,用于将字符串作为参数 { len = strlen(s); std = new char[len + 1]; strcpy_s(std, len + 1, s); num_strings++; //计数器+1 } String::String() //默认构造函数 { len = 6; std = new char[len + 1]; strcpy_s(std, len + 1, "未命名"); num_strings++; } String::String(const String & m) //复制构造函数,新建一个对象,并将其初始化为同类现有对象时调用,需要增加计数器 { len = m.len; std = new char[len + 1]; strcpy_s(std, len + 1, m.std); num_strings++; } String::~String() //析构函数,需要delete对象new出来的动态内存 { delete[]std; num_strings--; } String & String::operator=(const String & m) //赋值运算符重载,无需增加计数器 { delete[]std; //因为是已存在对象,然后下面要new,所以这里需要先delete len = m.len; std = new char[len + 1]; strcpy_s(std, len + 1, m.std); return *this; } String & String::operator=(const char*m) //赋值运算符重载,用于将字符串赋给类对象时调用,不增加计数器,因为这个用来避免创造临时对象 { if (*this == m)return *this; //防止将自己赋值给自己 delete[]std; //已存在对象,下面要new,因此delete len = strlen(m); std = new char[len + 1]; strcpy_s(std, len + 1, m); return *this; } char & String::operator[](int i) //中括号运算符重载,用于返回第i个字符 { return std[i]; } const char & String::operator[] (int i) const //中括号运算符重载,用于返回第i个字符,面向被const限定对象 { return std[i]; } bool operator<(const String &st, const String& st2) //用于比较类对象大小(实质上是比较类对象的字符串的数值大小——逐个比较) { return (strcmp(st.std, st2.std) < 0); } bool operator>(const String &st1, const String &st2) { return st2 < st1; } bool operator==(const String &st, const String &st2) { return (strcmp(st.std,st2.std) == 0); } ostream & operator<<(ostream & os, const String &st) //输出运算符重载 { os << st.std << "的长度是:" << st.len; return os; } istream & operator>>(istream & is, String &st) //输入运算符重载 { delete[]st.std; char *q = new char[100]; //用一个间接的储存输入的内容 is.get(q, String::CINLIM); while (is.get() != '\n')is.get(); //由于要求清除换行符,所以不能直接用is>>q这种形式,需要读取掉换行符。 st.len = strlen(q); st.std = new char[st.len + 1]; strcpy_s(st.std, st.len + 1, q); //然后复制进去 delete[]q; //删除这个间接的 return is; } int String::HowMany() //返回当前对象数量,注意,这里的定义不再加static表示是静态成员函数 { return num_strings; } //1.cpp,用于测试 #include<iostream> #include"1.h" const int ArSize = 10; const int MaxLen = 81; int String::num_strings = 0; //静态变量 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; 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 (int 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"; system("pause"); return 0; }
测试结果:
Hi,what's your name? >>wd wd的长度是:2, please enter up to 10 short sayings <empty line to quit>: 1: asda 2: befbe 3: qwwqfwqf 4: fdvfdvav 5: avavdasv 6: szccvdv 7: ththeth 8: q 9: fdgafg 10: rgerg Here are your sayings: a: asda的长度是:4 b: befbe的长度是:5 q: qwwqfwqf的长度是:8 f: fdvfdvav的长度是:8 a: avavdasv的长度是:8 s: szccvdv的长度是:7 t: ththeth的长度是:7 q: q的长度是:1 f: fdgafg的长度是:6 r: rgerg的长度是:5 Shortest saying: q的长度是:1 First alphabetically: asda的长度是:4 This program used 11 String objects. Bye. 请按任意键继续. . .
总结:
①犯错的一个地方在于:istream & operator>>(istream & is, String &st) //输入运算符重载 这个函数,只记得delete,但忘记new。因此退出程序时会出错,补充后ok。
②在①函数中,第一次未添加while (is.get() != '\n')is.get();出错,表现是未读取换行符。添加用于读取换行符后正常。
③没注意到原代码是在类成员函数定义文件中声明的静态变量,导致出错一次。在1.cpp文件中补充后正常。
④假如输入空行提前结束输入,会导致依然显示11 String objects,原因在于测试程序的循环是这样的,会要求建立10个新对象,再加上String name这个对象,因此共计11个。
⑤另一个错误在于,在赋值运算符重载时(面向类对象的),忘记加入面对自身时的条件判断了。这个错误会导致假如把自己赋值给自己,可能会出错的问题。