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

简介:

有比较函数是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个。

 

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

 


目录
相关文章
|
1月前
|
C++ 开发者
43运算符重载函数作为类成员函数和友元函数
43运算符重载函数作为类成员函数和友元函数
11 0
|
5月前
|
C++
35 C++ - 指针运算符(*、->)重载
35 C++ - 指针运算符(*、->)重载
32 0
|
5月前
|
C++
40 C++ - 符号重载总结
40 C++ - 符号重载总结
23 0
|
5月前
|
C++
38 C++ - 函数调用符号()重载
38 C++ - 函数调用符号()重载
18 0
|
6月前
|
存储 Swift
13 如何为类和结构体自定义运算符实现
如何为类和结构体自定义运算符实现
31 0
|
7月前
|
Java 编译器 程序员
C++中的语法知识虚继承和虚基类
多继承(Multiple Inheritance)是指从多个直接基类中产生派生类的能力,多继承的派生类继承了所有父类的成员。尽管概念上非常简单,但是多个基类的相互交织可能会带来错综复杂的设计问题,命名冲突就是不可回避的一个。 多继承时很容易产生命名冲突,即使我们很小心地将所有类中的成员变量和成员函数都命名为不同的名字,命名冲突依然有可能发生,比如典型的是菱形继承,如下图所示: 图1:菱形继承 类 A 派生出类 B 和类 C,类 D 继承自类 B 和类 C,这个时候类 A 中的成员变量和成员函数继承到类 D 中变成了两份,一份来自 A-->B-->D 这条路径,另一份来自 A-->C-->
121 1
|
10月前
|
安全 编译器 C++
C++ 引用,友元,运算符重载
C++ 引用,友元,运算符重载
|
11月前
|
C++
运算符重载的函数作为类的成员函数和友元函数
🐰运算符重载的函数作为类的成员函数和友元函数 🌸运算符重载定义为类的成员函数 🌸运算符重载函数作为类的友元函数 🌸实现“+”,“-“的普通重载函数和友元重载函数 🌸单目运算符"++"和"--"的重载
C++:运算符重载与类的赋值运算符重载函数
C++:运算符重载与类的赋值运算符重载函数