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

有比较函数是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];
}

显示:

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 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,所以这里需要先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 & isString &st) //输入运算符重载   这个函数,只记得delete,但忘记new。因此退出程序时会出错,补充后ok

 

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

 

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

 

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

 

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

 


上一篇:朋友面试被问到---静态构造函数


下一篇:使用goto跳转到switch的某个case