(一二八)比较成员函数、中括号表示法、静态成员函数

来源:互联网 发布:1.76我本沉默复古淘宝 编辑:程序博客网 时间:2024/04/27 23:06

有比较函数是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类型的比较,可以直接使用 >、<、= 比较2string类字符串的大小(返回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];}

显示:

baaa, 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 charMan::operator[](int m)

{

return lname[m];

}

这里的const表示返回值被const所限定,因此不能成为左值。

 

②假如是类对象被const所限定,如:const Man a;,这里表示对象的成员不能被修改。那么:

1)需要有符合其的重载定义(成员函数在括号后加const表示限定成员不能被修改);

2)而返回值不能直接返回引用(因为这样可能导致会被修改),应返回普通类型或者是被const所限定的引用。

如:

const charMan::operator[](int m)const

{

return lname[m];

}

第一个const表示返回值被限定,第二个const表示成员对象被限定。

 

 

③因此,如果要为被const限定的对象和不被const限定的对象(后者方便修改,且前提是返回值的成员没有被const限定)我们可以同时准备两个中括号运算符重载的定义,当遇见被const限定的对象时,则匹配const版的,否则匹配非const版的。

charMan::operator[](int m)

{

return lname[m];

}

const charMan::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,所以这里需要先deletelen = 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,因此deletelen = 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;elsesayings[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";}elsecout << "No input! Bye.\n";system("pause");return 0;}

 

测试结果:

Hi,what's your name?>>wdwd的长度是:2, please enter up to 10 short sayings <empty line to quit>:1: asda2: befbe3: qwwqfwqf4: fdvfdvav5: avavdasv6: szccvdv7: ththeth8: q9: fdgafg10: rgergHere are your sayings:a: asda的长度是:4b: befbe的长度是:5q: qwwqfwqf的长度是:8f: fdvfdvav的长度是:8a: avavdasv的长度是:8s: szccvdv的长度是:7t: ththeth的长度是:7q: q的长度是:1f: fdgafg的长度是:6r: rgerg的长度是:5Shortest saying:q的长度是:1First alphabetically:asda的长度是:4This program used 11 String objects. Bye.请按任意键继续. . .

 

 

总结:

①犯错的一个地方在于:istream & operator>>(istream & isString &st)//输入运算符重载   这个函数,只记得delete,但忘记new。因此退出程序时会出错,补充后ok

 

②在①函数中,第一次未添加while (is.get() != '\n')is.get();出错,表现是未读取换行符。添加用于读取换行符后正常。

 

③没注意到原代码是在类成员函数定义文件中声明的静态变量,导致出错一次。在1.cpp文件中补充后正常。

 

④假如输入空行提前结束输入,会导致依然显示11 String objects,原因在于测试程序的循环是这样的,会要求建立10个新对象,再加上String name这个对象,因此共计11个。

 

⑤另一个错误在于,在赋值运算符重载时(面向类对象的),忘记加入面对自身时的条件判断了。这个错误会导致假如把自己赋值给自己,可能会出错的问题。

 


0 0
原创粉丝点击