类和对象

1. 从结构体到类

结构体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
using namespace std;
struct Person
{
string name;
int id;
int score;
void PrintInfo(){
cout<<name<<'\t'<<id<<'\t'<<score<<endl;
}
};
int main(){
Person Eden;
Eden.name="MrEason";
Eden.id=110;
Eden.score=123;
Eden.PrintInfo();
return 0;
}
结构体和类的唯一区别:结构体默认权限是公有的,而类默认成员权限是私有的。只需声明权限即成为类的结构:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
using namespace std;
class Person
{
public:
string name;
int id;
int score;
void PrintInfo(){
cout<<name<<'\t'<<id<<'\t'<<score<<endl;
}
};
int main(){
Person Eden;
Eden.name="MrEason";
Eden.id=110;
Eden.score=123;
Eden.PrintInfo();
return 0;
}
类有public、private、protected三种权限,空类占1个字节,成员函数不占用类的空间,其他非空类具体计算同结构体,与写法、首地址、地址偏移、成员变量类型均有关系。

2. 类的多文件编译

根据不同的平台搭建多文件编译环境,目的是为编译器找到需要编译的cpp文件和头文件。以VsCode为例: 1. 在配置文件c_cpp_properties.json检查头文件路径

1
2
3
"includePath": [
"${workspaceFolder}/**",
],
说明新建头文件在该工作区下都可找到,无需修改。 2. 配置编译器: 在tasks.json将"${file}"更换为"${workspaceFolder}\main.cpp"、"${workspaceFolder}\person.cpp"等需要编译的cpp文件,全选可用*.cpp代替;为了确保编译器能找到头文件,还需增加参数"-I"代表增加头文件路径并在下一行加入头文件所在的文件夹
1
2
3
4
5
6
7
8
9
10
11
"args": [
"-fdiagnostics-color=always",
"-g",
//"${file}", //当前文件
"${workspaceFolder}\\main.cpp",//多文件编程
"${workspaceFolder}\\person.cpp",
"-I",
"C:/Users/24364/C_git/"//头文件所在文件夹
"-o",
"${fileDirname}\\${fileBasenameNoExtension}.exe"
],
至此VC多文件编译环境配置完成,把声明置于头文件,类外函数实现置于cpp文件,主函数在main函数即可,测试:多文件编译实现信息打印
1
2
3
4
5
6
7
8
9
10
11
12
13
//person.h
#ifndef PERSON_H
#define PERSON_H
#include <iostream>
class Person
{
public:
std::string name; //string需要std支持
int id;
int score;
void PrintInfo();
};
#endif
1
2
3
4
5
6
7
8
9
//person.cpp
#include <iostream>
#include <person.h>
using namespace std;

void Person::PrintInfo(){

cout<<name<<'\t'<<id<<'\t'<<score<<endl;
}
1
2
3
4
5
6
7
8
9
10
11
12
//main.cpp
#include <person.h>
#include <iostream>
using namespace std;
int main(){
Person Eden;
Eden.name="MrEason";
Eden.id=110;
Eden.score=1234;
Eden.PrintInfo();
return 0;
}
string是C++标准库定义的一个字符串类,因此在头文件中要包含iostream外,还要命名空间std的支持。

3. 类的初始化与赋值

公有的对象可以在类外进行初始化,而私有对象无法直接初始化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include<iostream>
using namespace std;
class Person
{
public:
std::string name;
int id;
int score;
void PrintInfo();
};
void Person::PrintInfo(){

cout<<name<<'\t'<<id<<'\t'<<score<<endl;
}
int main(){
Person Eden;
Eden={"MrEason",110,1234};//公有对象直接赋值初始化
// Eden.name="MrEason";
// Eden.id=110;
// Eden.score=1234;
Eden.PrintInfo();
return 0;
}

两种类成员实例化方法

1
2
3
4
5
6
Person Ming =Eden;//1.调用拷贝构造函数创建
Ming.PrintInfo();

Person Yao;
Yao=Eden; //2.调用等号运算符函数重载创建
Yao.PrintInfo();

拷贝构造函数:本质上是一种值传递,将实例化Eden的成员逐一拷贝到新实例Ming中,这对普通变量是可行的,但是如果类中成员有指针变量,则会造成浅拷贝问题,也即两个指针变量同时指向一个地址,导致内存释放时如果释放两次会造成程序出错。

4. C++类三个自动调用的成员函数

1. 构造函数:

与类同名的函数可重载,用于实例化对象时自动初始化。无参构造两种有参传值方法四种调用方法形式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include<iostream>
using namespace std;
class Person
{
public:
//无参构造函数和有参构造函数
Person(){
cout<<"This is a test"<<endl;
}
//直接传参:区分形参与类成员变量
Person(string name,int id,int score){
pname=name;
pid=id;
pscore=score;
cout<<pname<<'\t'<<pid<<'\t'<<pscore<<endl;
}
//链表传参:可以混用形参与类成员变量
// Person(string pname,int pid,int pscore):
// pname(pname),pid(pid),pscore(pscore){
// cout<<pname<<'\t'<<pid<<'\t'<<pscore<<endl;
// }
string pname;
int pid;
int pscore;
//void PrintInfo();
};
// void Person::PrintInfo(){
// cout<<pname<<'\t'<<pid<<'\t'<<pscore<<endl;
// }
int main(){

//1.隐式调用
Person Eden; //自动调用无参构造
Person Ming("Mike",12,123); //自动调用有参构造

//2.显式调用
Person Eden1=Person();
Person Ming1=Person("Mike",12,123);

//3.动态开辟空间调用
Person *a=new Person; //无参
Person *b=new Person(); //无参
Person *c=new Person("Mike",12,123); //有参

//4.匿名对象调用:生存周期很短,定义开辟、执行完毕自动释放,常用于临时对象的赋值
Person();
Person("Mike",12,123);
}
只有以上述四种方法实例化对象才可能调用构造函数,如果是作为形参、或者实例化新对象接收返回值,均不会发生调用

2. 析构函数:

波浪线~+类名标识一个析构函数,析构函数没有参数,也没有返回值,void不写,唯一且不能重载,用于释放成员变量的空间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include<iostream>
using namespace std;
class Person
{
public:
//无参构造函数和有参构造函数
Person(){
cout<<"This is a test"<<endl;
}
Person(string name,int id,int score){
pname=name;
pid=id;
pscore=score;
cout<<pname<<'\t'<<pid<<'\t'<<pscore<<endl;
}

//析构函数
~Person(){
cout<<"This is a Destructor"<<endl;
}

string pname;
int pid;
int pscore;
//void PrintInfo();
};
// void Person::PrintInfo(){
// cout<<pname<<'\t'<<pid<<'\t'<<pscore<<endl;
// }
int main(){
Person Eden; //自动调用无参构造
Person Ming("Mike",12,123); //自动调用有参构造
return 0;

}

结果:This is a test Mike 12 123 This is a Destructor This is a Destructor //两个对象销毁两次均调用

堆区、栈区、全局区、静态区类对象的析构函数调用时机:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include<iostream>
using namespace std;
class Person
{
public:
//无参构造函数和有参构造函数
Person(){
cout<<"This is a test"<<endl;
}
//直接传参:区分形参与类成员变量
Person(string name,int id,int score){
pname=name;
pid=id;
pscore=score;
cout<<pname<<'\t'<<pid<<'\t'<<pscore<<endl;
}
//析构函数
~Person(){
cout<<"This is a Destructor!"<<endl;
cout<<this<<endl;//打印当前类对象指针
}
string pname;
int pid;
int pscore;
//void PrintInfo();
};
// void Person::PrintInfo(){
// cout<<pname<<'\t'<<pid<<'\t'<<pscore<<endl;
// }
void test(){
//函数普通变量,栈区空间
Person Eden;
Person Ming("Mike",12,123);

static Person Yin;
cout<<"static"<<&Yin<<endl;
Person *Yao=new Person;//无参构造函数堆区开辟
}

Person Glb; //全局变量

int main(){

test();
cout<<"Flag1"<<endl;

return 0;
}

结果This is a test //全局变量实例化 This is a test //test普通变量无参 Mike 12 123 //test普通变量有参 This is a test //静态变量 static0x408080 //静态变量地址 This is a test //堆区 This is a Destructor! //普通无参触发 0x61fd50 This is a Destructor! //普通有参触发 0x61fd80 Flag1 This is a Destructor! //静态变量触发 0x408080 This is a Destructor! //全局区触发 0x408040

结果综述普通函数类对象的变量存储在栈区,在完成函数执行即释放内存静态变量在return 0前进行释放全局变量在程序执行完成,return 0时释放堆区开辟的空间可以手动delete释放,如果没有则在程序结束以后才释放内存,因为程序结束在前,故看不到析构函数调用现象了。

3. 拷贝构造函数:

与类同名,但接受的参数是本类对象参数,系统的拷贝构造函数底层实现类似代码所示,通过引用保证是值传递(而不会开辟新的空间)。而系统的构造函数、析构函数默认均为无参

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include<iostream>
using namespace std;
class Person
{
public:
//无参构造函数和有参构造函数
Person(){
cout<<"This is a test"<<endl;
}
Person(string name,int id,int score){
pname=name;
pid=id;
pscore=score;
cout<<pname<<'\t'<<pid<<'\t'<<pscore<<endl;
}
//析构函数
~Person(){
cout<<"This is a Destructor"<<endl;
}
//拷贝构造函数
Person(const Person &obj){
cout<<"This is a Copy constructor"<<endl;
pname=obj.pname;
pid=obj.pid;
pscore=obj.pscore;
cout<<pname<<'\t'<<pid<<'\t'<<pscore<<endl;
}

string pname;
int pid;
int pscore;
//void PrintInfo();
};
// void Person::PrintInfo(){
// cout<<pname<<'\t'<<pid<<'\t'<<pscore<<endl;
// }
int main(){
Person Eden; //自动调用无参构造
Person Ming("Mike",12,123); //自动调用有参构造
Person Yao=Ming; //自动调用拷贝构造函数
return 0;
}

构造函数不同,类拷贝构造函数除了在直接赋值调用,还可以在接受类对象实参为形参返回值(没有接受则视为匿名)的情况下均会调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include<iostream>
using namespace std;
class Person
{
public:
//无参构造函数和有参构造函数
Person(){
cout<<"This is a test"<<endl;
}
//直接传参:区分形参与类成员变量
Person(string name,int id,int score){
pname=name;
pid=id;
pscore=score;
//cout<<pname<<'\t'<<pid<<'\t'<<pscore<<endl;
}
Person(const Person &obj){
cout<<"This is a Copy Constructor"<<endl;
pname=obj.pname;
pid=obj.pid;
pscore=obj.pscore;
//cout<<pname<<'\t'<<pid<<'\t'<<pscore<<endl;

}
string pname;
int pid;
int pscore;
};
//类作为形参
void printInfo(Person obj){
cout<<obj.pname<<'\t'<<obj.pid<<'\t'<<obj.pscore<<endl;
}
//类作为返回对象
Person Return_OBJ(){
static Person Ming; //static要保证类存活到运行完成test
return Ming;
}

void test(){
Person Eden("Mike",112,122);
printInfo(Eden);
Return_OBJ();

}
int main(){

test();

return 0;
}

构造、析构、拷贝构造函数解决类指针变量使用问题

构造函数为大批量的类实例化提供了便利,但在类中如果涉及指针变量堆区开辟空间(如char*字符串等),应该通过手动方式进行释放,否则会导致大量的空间被占用,析构函数就完成了这个任务,但系统的拷贝构造函数会引起前文所说的浅拷贝问题,导致析构函数执行异常,因此应该以深拷贝方式进行调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#include<iostream>
#include<cstring>
using namespace std;
class Person
{
public:
//无参构造函数
Person(){
name=NULL;
slga=NULL;
id=0;
score=0;
cout<<"This is non-parameter constructor"<<endl;
}
//有参构造函数
Person(char *pname,char *pslga,int pid,int pscore)
{
name=new char[strlen(pname)+1];
strcpy(name,pname);
slga=new char[strlen(pslga)+1];
strcpy(slga,pslga);
id=pid;
score=pscore;
cout<<"This is parameter constructor"<<endl;
}
//析构函数
~Person(){ //假如是无参构造,堆区为空,则无需释放内存
if(name!=NULL){
delete []name;
name=NULL;
cout<<"This is a name-Destructor"<<endl;
}
if(slga!=NULL){delete []slga;
slga=NULL;
cout<<"This is a slga-Destructor"<<endl;
}

}
Person(const Person &obj){
cout<<"This is a Copy Constructor"<<endl;

//深拷贝
name=new char[strlen(obj.name)+1];
strcpy(name,obj.name);

slga=new char[strlen(obj.slga)+1];
strcpy(slga,obj.slga);

id=obj.id;
score=obj.score;
}
void printInfo(){
cout<<name<<'\t'<< slga<<'\t'<<id<<'\t'<<score<<endl;
}
private:
char *name;
char *slga;
int id;
int score;

};

int main(){
//C++更建议使用string,string本身底层使用深拷贝,字符串赋予char*会报警告
Person Eden("Mike","Study is Happy",12,123);
Eden.printInfo();

//拷贝test
Person Ming=Eden;
Ming.printInfo();

return 0;
}
执行成功,代表析构函数正常销毁。

5. this指针

类对象实例化后都会将自己地址传入大多数的成员函数中(不是全部),方便调用:如通过this可以特指实例化的成员而与形参区分。this地址与实例化类地址将保持一致:

1
2
3
4
5
6
7
8
9
10
11
//有参构造函数
Person(char *name,char *slga,int pid,int score)
{
this->name=new char[strlen(name)+1];
strcpy(name,pname);
this->slga=new char[strlen(slga)+1];
strcpy(slga,slga);
this->id=pid;
this->score=score;
cout<<"This is parameter constructor"<<endl;
}

6. 类对象数组

进行局部初始化初始化部分调用有参构造函数,其余部分调用的是无参构造函数

1
Person Array[6];// 定义对象数组

7. 类成员对象

另一个类作为该类成员变量,称类成员对象。实例化时,首先调用类成员对象的构造函数后调用本类的构造函数首先调用本类的析构函数后再调用类成员对象的析构函数。假如类成员对象的成员变量在原类中被赋予私有权限,则一般通过原类的公有函数进行初始化操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
#include<iostream>
#include<cstring>
using namespace std;
class Pysicque
{
public:
Pysicque(){
cout<<"This is a new non-parameter constructor"<<endl;
}
//同名链表传参
Pysicque(int height,int weight):
height(height),weight(weight)
{
cout<<"This is a new parameter constructor "<<endl;

}
~Pysicque(){
cout<<"This is a new destructor"<<endl;
}
//两个公有函数操作私有成员
void set_psyinfo(int height,int weight){
this->height=height;
this->weight=weight;
}
void printInfo(){
cout<<height<<'\t'<<weight<<'\t'<<endl;
}
private:
int height;
int weight;
};

class Person
{
public:
//无参构造函数
Person(){
name="";//空
slga="";
id=0;
score=0;
cout<<"This is non-parameter constructor"<<endl;
}
//有参构造函数
Person(string name,string slga,int pid,int score,int height,int weight)
{
this->name=name;
this->slga=slga;
this->id=pid;
this->score=score;
Pys.set_psyinfo(height,weight);
cout<<"This is parameter constructo!"<<endl;
}
//析构函数
~Person(){ //假如是无参构造,堆区为空,则无需释放内存
cout<<"This is a destructor!"<<endl;
}
// Person(const Person &obj){
// cout<<"This is a Copy Constructor"<<endl;
// name=obj.name;
// slga=obj.slga;
// id=obj.id;
// score=obj.score;
// }
void printInfo(){
cout<<name<<'\t'<< slga<<'\t'<<id<<'\t'<<score<<endl;
Pys.printInfo();
}

private:
string name;
string slga;
int id;
int score;
Pysicque Pys;
};
int main(){
Person Eden("Mike","I hate study",1,100,200,100);// 定义对象数组
Eden.printInfo();
return 0;
}

8. 常函数和常对象

常函数、常对象是被const修饰成员函数实例化类,有以下特点: 常函数:一般用于获取成员变量的值,而不进行修改,如果修改必须用mutable关键字修饰对应成员对象;
常对象常对象可以正常调用构造函数,但无法调用普通的成员函数,只能调用常函数无法修改普通成员变量,只能修改公有且被mutable关键字修饰的成员变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include<iostream>
#include<cstring>
using namespace std;
class Person
{
public:
//无参构造函数
Person(){
name="";//空
slga="";
id=0;
score=0;
cout<<"This is non-parameter constructor"<<endl;
}
//有参构造函数
Person(string name,string slga,int pid,int score)
{
this->name=name;
this->slga=slga;
this->id=pid;
this->score=score;
cout<<"This is parameter constructo!"<<endl;
}
//常函数
void printInfo() const
{
score=100;
cout<<name<<'\t'<< slga<<'\t'<<id<<'\t'<<score<<endl;
}

private:
string name;
string slga;
int id;
mutable int score; //常函数要修改的值必须用mutable修饰

};

int main(){
Person Eden("Mike","I hate study",1,99);//
Eden.printInfo();

//常对象
const Person const_Eden;
const_Eden.printInfo();
const Person const_Eden1("Mike","I hate study",1,98);
const_Eden1.printInfo();
return 0;
}

9. 静态成员变量和静态成员函数

静态成员变量

  1. 静态成员变量必须在类外进行声明初始化才能在类内正常使用。
  2. 静态成员变量归属于类不是对象,类的所有实例化对象共用一个静态成员变量,所以一般赋予公有权限,通过类名+域解析符类外来直接操作。

静态成员函数:

  1. 静态成员函数内不能使用this指针
  2. 静态成员函数只能操作静态成员变量不能操作普通成员变量
  3. 静态成员函数同样属于类而不属于对象,通过类名+域解析符调用。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    #include<iostream>
    #include<cstring>
    using namespace std;
    class Person
    {
    public:
    static int num;//类内声明:静态成员变量
    //无参构造函数
    Person(){
    name="";//空
    slga="";
    id=0;
    score=0;
    cout<<"This is non-parameter constructor"<<endl;
    }

    //有参构造函数
    Person(string name,string slga,int pid,int score)
    {
    this->name=name;
    this->slga=slga;
    this->id=pid;
    this->score=score;
    cout<<"This is parameter constructo!"<<endl;
    }
    //静态成员函数:不能含this,不能含普通成员变量
    static void test(){
    cout<<"静态变量起始值:"<<num<<endl;
    Person::num=999;
    cout<<"静态变量更新值:"<<num<<endl;
    }
    void printInfo()
    {
    cout<<name<<'\t'<<slga<<'\t'<<id<<'\t'<<score<<endl;
    }

    private:
    string name;
    string slga;
    int id;
    int score; //常函数要修改的值必须用mutable修饰

    };

    int Person::num=88;//静态变量:一定要在类外初始化,不能声明static

    int main(){
    Person::test(); //类 调用 静态成员函数
    Person Eden;
    Person Ming;
    Eden.num=99; //类对象 修改 静态变量
    cout<<Ming.num<<endl;//其他对象会同步修改
    //Person::num=99;//类访问也是修改

    Eden.test();//对象 调用 静态成员函数
    cout<<Ming.num<<endl;

    return 0;
    }

10. 友元

10.1 友元函数(全局)

在类内声明一个friend修饰的函数称为该类的友元函数友元函数类外也可对本类的私有成员进行访问和操作

1
friend void test(parameter);

10.2 友元类

类内声明友元类友元类所有成员函数都可以对该类的所有成员进行访问和操作,但不能通过友元类在类外直接对该类的私有成员进行访问和操作。例如,B类被声明为A类的友元,只能在B类的成员函数实现调用A类的私有成员变量,而不能类外实例化B类后,通过B访问A的成员变量。

1
friend class test

10.3 友元成员函数

一个类的成员函数可在另一个类中被声明成友元函数该成员函数允许访问和操作另一个类的私有成员,比起友元类这种方法具有更高保护性,但有繁琐的顺序要求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include<iostream>
#include<cstring>
using namespace std;
//声明第一个类:供给成员函数的
class Person
{
public:
void printInfo(); //要设置成另一个类的友元成员函数只声明不实现
Person(string name,string slga,int pid,int score)
{
this->name=name;
this->slga=slga;
this->id=pid;
this->score=score;
cout<<"This is parameter constructo!"<<endl;
}
private:
string name;
string slga;
int id;
int score;
};
//第二个类:通过上一个类某个成员函数访问该类的私有成员
class Pysicque
{
friend void Person::printInfo();//声明友元函数
public:
Pysicque(int height,int weight):
height(height),weight(weight){cout<<"This is a new parameter constructo!"<<endl; }

void Pys_printInfo(){
cout<<height<<'\t'<<weight<<'\t'<<endl;
}
private:
int height;
int weight;
};
//友元函数类外实现
void Person::printInfo(){
Pysicque Pys(100,100);
cout<<name<<'\t'<<slga<<'\t'<<id<<'\t'<<score<<endl;
cout<<Pys.height<<'\t'<<Pys.weight<<'\t'<<endl;
}

int main(){
//测试友元函数能否获取信息
Person Eden("Mike","I hate study",1,100);
Eden.printInfo();
return 0;
}

11. 运算符重载

C++支持运算符重载,即不同的数据类型可以根据函数定义进行运算,适应特殊的计算需求。运算符重载的本质是更简洁的函数调用,增加代码易读性, 一般重载函数格式为:

1
数据类型 operator@(parameter){...}//@指代需要重载的运算符
支持重载运算符:图自链接

支持的重载运算符 不支持重载运算符: 成员访问运算符(.)、域解析符(::)、长度运算符(sizeof)、成员指针运算符(->*、.*)、条件运算符(?:)、预处理运算符(#)

11.1 公有成员实现运算符重载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include<iostream>
#include<cstring>
using namespace std;
//声明第一个类:供给成员函数的
class Person
{
public:
Person(string name,int pid,int score)
{
this->name=name;
this->id=pid;
this->score=score;
cout<<"This is parameter constructo!"<<endl;
}
void printInfo(){
cout<<name<<'\t'<<id<<'\t'<<score<<endl;
}
string name;
int id;
int score;
};
//实现的是obj++,后缀++参数表加上int
int operator++(Person &obj,int){
//后缀++赋值后自增
int temp=obj.score;
obj.score++;
return temp;
}
//实现的是++obj,前缀++无需加int
int operator++(Person &obj){
//前缀++自增后赋值
obj.score++;
int temp=obj.score;
return temp;
}
int main(){
Person Eden("Mike",12,0);
int m=Eden++; //输出m=0
cout<<"m="<<m<<endl;
int n=++Eden; //输出n=2
cout<<"n="<<n<<endl;
return 0;
}

结果:m=0、n=2 后缀++是m先接收后增值,n是先增值后接收输出

11.2 私有成员实现运算符重载

方法一:类内公有函数

类内实现本身就包含一个类对象,this指针可以引用,因此比外部实现可减少一个形参,如obj+obj只需要传入一个Person &obj作为形参,obj++则无需传入Person类。

方法二:友元函数

类中声明即可,不赘述。

1
2
friend int operator++(Person &obj,int);
friend int operator++(Person &obj);

12. 代码实战:类与运算符重载实现string类

字符串的char*、char[]类型使用麻烦,且容易造成非法的内存操作,因此C++封装了一种string类,大大提高了使用字符串的鲁棒性。本代码目的根据前述知识模拟封装一个简化的“string类”,具备赋值、打印、合并、比较等简单功能。采用多文件编译环境方便移植,参考如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//Testring.h
#ifndef Testring_H
#define Testring_H
#include<iostream>
#include<cstring>
using namespace std;
class Testring{
public:
Testring(){
T_string=NULL;
size=0;
}
Testring(char *str){
this->size=strlen(str);
this->T_string=new char[strlen(str)+1];
strcpy(T_string,str);
}
char *T_string; //字符串内容
int size; //字符串长度
void printString(); //字符串打印

Testring& operator=(Testring& tempstr); //串类赋值
Testring& operator=(char* tempstr); //常量赋值

char& operator[](int index); //中括号重载,字符串索引寻址

Testring& operator+(Testring &tempstr); //加号重载,字符串追加
Testring& operator+(char *tempstr); //加号重载,字符串追加

bool operator==(Testring &tempstr);//字符串类间比较
bool operator==(char *tempstr); //字符串与字符串常量比较
};
ostream& operator<<(ostream& os,Testring& str); //全局的运算符重载函数,所以需要两个参数

#endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
//Testring.cpp
#include<iostream>
#include<Testring.h>
using namespace std;
//打印长度、字符串内容
void Testring::printString(){

cout<<"string:"<<T_string<<endl;
cout<<"size:"<<size<<endl;
}

//输出流重载打印
ostream& operator<<(ostream& os,Testring& str){
cout<<"string:"<<str.T_string<<endl;
cout<<"size:"<<str.size<<endl;
return cout; //返回cout的目的是能实现连续输出,cout<<xxx<<xxx...
}

//类对象传递重载
Testring& Testring::operator=(Testring& tempstr){
if(this==&tempstr){ //A1:同地址不传递
return *this;
}
else{
if(this->T_string!=NULL){
delete []this->T_string; //Q1:直接delete影响了自身传值
this->T_string=new char[tempstr.size+1];
strcpy(this->T_string,tempstr.T_string);
this->size=tempstr.size;
}
else{
this->T_string=new char[tempstr.size+1];
strcpy(this->T_string,tempstr.T_string);
this->size=tempstr.size;
}
return *this;
}
}
//字符串传递重载
Testring& Testring::operator=(char* tempstr){
if(this->T_string!=NULL){
delete []this->T_string;
this->T_string=new char[strlen(tempstr)+1];
strcpy(this->T_string,tempstr);
this->size=strlen(tempstr);
}
else{
this->T_string=new char[strlen(tempstr)+1];
strcpy(this->T_string,tempstr);
this->size=strlen(tempstr);
}
return *this;
}
//索引寻值重载
char& Testring::operator[](int index){
if(index>=0&&index<size){
return T_string[index];
}
else{
cout<<"Sorry,out of Index!"<<endl;
}
}
//字符串增重载
Testring& Testring::operator+(Testring& tempstr){
if(this->T_string!=NULL){
char *temp=new char[this->size+1];
strcpy(temp,this->T_string);
delete []this->T_string;
this->T_string=new char[this->size+tempstr.size+1];
strcpy(this->T_string,temp);
strcat(this->T_string,tempstr.T_string);
this->size+=tempstr.size;
delete []temp;
}
else{
this->T_string=new char[tempstr.size+1];
strcpy(this->T_string,tempstr.T_string);
this->size+=tempstr.size;
}
return *this;

}

Testring& Testring::operator+(char *tempstr){
if(this->T_string!=NULL){
char *temp=new char[this->size+1];
strcpy(temp,this->T_string);
delete []this->T_string;
this->T_string=new char[this->size+strlen(tempstr)+1];
strcpy(this->T_string,temp);
delete []temp;
strcat(this->T_string,tempstr);
this->size+=strlen(tempstr);
}
else{
T_string=new char[strlen(tempstr)+1];
strcpy(T_string,tempstr);
this->size+=strlen(tempstr);
}
return *this;
}

bool Testring::operator==(Testring &tempstr){
if(tempstr.size!=this->size){
return false;
}
else{
if(tempstr.T_string!=this->T_string){return false;}
else return true;
}
}
bool Testring::operator==(char *tempstr){
if(strlen(tempstr)!=this->size){
return false;
}
else{
if(strcmp(tempstr,this->T_string)!=0){
cout<<"sasa"<<strcmp(tempstr,this->T_string)<<endl;
return false;} //strcmp判断相等返回0,不等返回-1;
else return true;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//main.cpp
#include <Testring.h>
#include <iostream>
using namespace std;
//主测试函数
int main(){
//打印
Testring string1("Hello World");
Testring string4("Hello Eden");
string1.printString(); //函数打印
cout<<string1<<string4; //输出流打印
//寻值
cout<<"-------------寻值------------------"<<endl;
cout<<string1[2]<<endl;
//常量赋值
cout<<"--------------常量赋值-----------------"<<endl;
char *test="Hello Eden";
Testring string2;
string2=test;
string2.printString();
string2=string2+"bye World";
string2.printString();
//类间赋值
cout<<"--------------类间赋值-----------------"<<endl;
Testring string3("Hello");
string3.printString();
string3=string3+string2+string1+string1;
string3.printString();
//字符串比较
cout<<"--------------字符串比较-----------------"<<endl;
cout<<(string3==string2)<<endl;
string3.printString();
cout<<(string3=="HelloHello Edenbye WorldHello WorldHello World")<<endl;
}