一、类
1.定义:
类是具有相同属性和方法的一类对象集合的抽象。它包含数据抽象(数据成员)和行为抽象(即成员函数)。【有哪些人,干哪些事】定义类的过程就是对问题进行抽象和封装的过程。
补充OOP特征:
【封装、继承、多态】
1.封装:
面向过程编程:数据和处理数据的函数是分开的,通过参数传递数据。(如C中的main函数)
面向对象编程:一切都是对象,类把对象的数据和操作数据的方法作为一个整体考虑。(封装)
2.(公有)继承:
继承了某对象将拥有该对象的公有属性和方法,并且还可以自己拓展添加自己的属性和方法。
代码实现
#include <iostream>
using namespace std;
class Entity
{
public:
float X, Y;//分别占4个字节
void Move(float xa,float ya)
{
X += xa;
Y += ya;
}
};
class Player :public Entity//inheritance
{
public:
const char* Name;//占64/8=8个字节
void PrintName()
{
cout << Name << endl;
}
};
int main()
{
cout << sizeof(Player) << endl;//一共是4+4+8=16个字节
Player player;
player.Move(5, 6);
player.X = 2;
return 0;
}
多态性
“多态(英语:polymorphism),是指计算机程序运行时,相同的消息可能会送给多个不同的类别之对象,而系统可依据对象所属类别,引发对应类别的方法,而有不同的行为。简单来说,所谓多态意指相同的消息给予不同的对象会引发不同的动作称之。”其实更简单地来说,就是“在用父类指针调用函数时,实际调用的是指针指向的实际类型(子类)的成员函数”。多态性使得程序调用的函数是在运行时动态确定的,而不是在编译时静态确定的。
简单的说就是能够去重写继承对象的方法,被利用的最多的例子莫过于ToString()
方法了,我们查看语言的类库就可以知道,其实可以知道每个类(对象)都有ToString()
方法,作用通常是输出对象的字符串信息。
虚函数 (virtual function) 指可以被子类继承和覆盖的函数。
基类声明成员函数为虚函数的方法:
virtual [类型] 函数名([参数表列])
2.类的一般形式
/*class Name
{
public://公有数据和函数(可以通过对象来访问)
protected://保护数据和函数(如果没有继承机制, 则其性质同private)
private://私有数据和函数(被隐藏的,外界看不见)
}*/
#include <iostream>
using namespace std;
class Point
{
private://外界看不到我,也无法调用我
int x, y;
public://外界可以看见,并且能够调用,而且我们内部(即成员函数或友元函数)互相可以看见
void set1(int a, int b)
{
x = a;
y = b;
}
void print()
{
cout << "x=" << x << endl;
cout << "y=" << y << endl;
}
};
int main()
{
Point p1;
//p1.x=1;//error
//p1.y=2;//error
p1.set1(1, 2);
p1.print();
return 0;
}
注意:1.私有成员只有该类的成员函数或者友元函数可以访问。
保护成员也不能通过对象访问,但是可以被该类或派生类的成员函数访问。【儿子请求访问】
公有成员定义了类的外部接口。
2.类的外部一般不直接访问(读和写)对象成员,而是用成员函数。(面向对象的思想)
3.结构体成员缺省为public,类的成员缺省为private(作用是保留系统隐私)。
4.类一般不用memset()来清空成员变量,而应该写一个专用于清空成员变量的成员函数。
对类和对象用sizeof运算意义不大,一般不用。
3.类的实现
成员函数的实现,可以放在类体内,也可以放在外面,但是必须在类体内声明。
放在类体内定义的函数被默认为内联函数,而放在类体外定义的函数是一般函数,如果要定义成内联函数需要在前面加上关键字inline(不然如何区分普通函数和成员函数呢),如:
public://外界可以看见,并且能够调用,而且我们内部(即成员函数或友元函数)互相可以看见
void set1(int a, int b)
{
x = a;
y = b;
}
void print();//体内声明
};
inline Point::print()//体外实现后,需要加上inline,其中::表示作用域
{
cout << "x=" << x << endl;
cout << "y=" << y << endl;
};
int main();
二、对象
1.对象的声明
2.对象的访问
【原点式】对象名.成员名 或 (*指向对象的指针).成员名
【指针式】对象指针->成员名 或 (&对象名)->成员名
三、类的界面与实现
3.1 定义
类的界面:是指将类封装好,通常放在.h头文件内
类的实现:通常是将类的成员函数实现过程单独放在.cpp文件内,并且注意要加上头文件<类名.h>
3.2 (普通)构造函数
(1)语法:类名(){ }
(2)访问权限必须是public,就是必须放在公有区。
(3)没有返回值,也不需写void。
(4)构造函数可以有参数,也可以有重载,可以有默认参数。
(5)构造函数实际上就是在创建类时,默认在所有动作之前自动对变量进行初始化为空的一个动作,防止乱码。且只会自动调用一次,不能手工调用!
(6)意义:不需要的话,自定义它干嘛,只要你写出这个结构,系统自动为你初始化。
3.3 析构函数
(1)语法:~类名(){}
(2)访问权限public
(3)没有返回值,也不需写void。
(4)没有参数,不能重载!
(5)销毁对象前只会自动调用一次,但是可以手工调用(但是基本不需要该功能)。
(6)在构造函数名后面加括号和参数不是调用构造函数,而是创建匿名对象。
如果类中成员有指针,若没有使用到构造函数,并且在其他地方没有为指针分配内存,那么该指针就是野指针。或者释放内存的代码放在析构函数中,如果没有用到析构函数导致指针成为野指针,可能会导致系统崩溃,所以一定要保证使用到构造/析构函数。
注意:
1.如果不编译构造/析构函数,编译器会自动提供空实现的构造/析构函数【也叫默认构造函数】,也只能调用空白参数的构造/析构函数。
2.创建对象时,如果重载了构造函数,编译器会根据实参来匹配相应的构造函数。
『方法重载主要好处就是不用为了对不同的参数类型或参数个数,而写多个函数。多个函数用同一个名字,但参数表,即参数的个数或(和)数据类型可以不同,调用的时候,虽然方法名字相同,但根据参数表可以自动调用对应的函数。』
3.不建议在构造/析构函数中写太多代码,可以使用成员变量减少工作量。
如果调用的无参构造函数,使用类名即可【没有括号】;调用有参的构造函数一定要加上实参!
在创建对象时不用加空的圆括号,编译器会误认为是声明函数。在构造函数后加括号和参数不是调用构造函数,而是创建匿名对象
4.注意调用构造函数时,调用函数参数和其定义的参数类型必须对应,否则编译器找不到调哪个。
Point P1();//实际是声明一个返回值类型为Point的函数,类似于int girl(),未创建对象。
Point ;//无效操作,没有函数名,相当于光写了一个int在这。
Point P1;//创建一个类名为P1的Point类,并调用Point中的无参构造函数(默认构造函数)
Point("Steve")//调用了Point中有一个string参数的构造函数。
#pragma once
#include "iostream"//不用加.h
#include <string>
using namespace std;
class student//创建一个学生的类,用于记录某人的序号、名字和三科成绩
{
public:
int m_num;
string m_name;
float m_score[3];
student();//默认的无参构造函数,类外定义
student(int age, string name, float score[],int n);//类内声明带参数的构造函数,类外定义,后面定义后,绿色波浪线会消失
//~student()//析构函数
//{
// cout << "destroy!" << endl;
//}
void display();
};
inline student::student()
{
m_num = 0;
m_name = "null";
for (int i = 0; i < 3; i++)
{
m_score[i] = 0;
}
cout << "使用了默认构造函数" << endl;
}
inline student::student(int age, string name, float score[],int n)
{
m_num = age;
m_name = name;
for (int i = 0; i < n; i++)
m_score[i] = score[i];
cout << "使用了带参数的构造函数" << endl;
}
inline void student::display()
{
cout << m_num << '\t' << m_name << '\t';
for (int i = 0; i < sizeof(m_score)/sizeof(m_score[0]); i++)
cout << m_score[i] << '\t';
cout << endl;
}
int main()
{
float s[3] = { 93.2f,89.0f,97.2f };
int len = sizeof(s) / sizeof(s[0]);
student s1;
student s2(100, "Alan", s, len);
//数组的个数需要以单独的变量传入
s1.display();
s2.display();
return 0;
}
PS:匿名对象和普通对象的区别:
1,形式不同:
创建一个普通对象: Userp = new User(); || 创建一个匿名对象:new User();
2.内存分布不同:匿名对象创建的对象只存在于堆中;非匿名对象创建对象时的对象虽然也在堆中,但其类变量却在栈中,栈中的类变量通过创建变量的内存地址来指向相应的对象。
int main()
{
student s1;
student();//创建一个student类的匿名对象,执行完该行代码后,对象消失,被析构函数运行。
student s2 = student();//创建一个student类的匿名对象,赋值给s2,则其周期变为s2的周期
student* s3 = new student;//创建一个student类的变量的空间,由g3这个指针指向
student* s4 = new student();//创建一个student类匿名对象的“空间”
delete s3;
delete s4;
return 0;
}
运行结果
Created constructor!//s1的构造函数
Created constructor!
Destroyed! //匿名对象立即被析构函数清除
Created constructor!//s2
Created constructor!//s3
Created constructor!//s4
Destroyed! //s1被清除
Destroyed! //s2被清除
Destroyed! //s3被清除
Destroyed! //s4被清除
3.4 拷贝构造函数
3.4.1 默认拷贝构造函数
(1)意义:
用已经存在的对象创建新的对象
(2)语法:
用一个已经存在的对象创建新对象的语法:
类名 新对象名(已存在的对象名)
类名 新对象名=已经存在的对象名
CGirl g1;//普通构造函数
CGirl g2(g1);
//CGirl g2=g1;
#pragma once
#include "iostream"
using namespace std;
class student
{
public:
int m_num;
string m_name;
void display();
student();
~student()
{
cout << "Destroyed!" << endl;
}
};
student::student()
{
m_num = 0;
m_name = "null";
cout << "Created constructor!" << endl;
}
void student::display()
{
cout << m_num << '\t' << m_name << '\t';
cout << endl;
}
int main()
{
student s1;
s1.m_num = 2;
s1.m_name = "Alan";
student s2(s1);//将s1复制给s2,此时不会调用(普通)构造函数
//而是调用系统默认的拷贝构造函数。
//student s2=s1;
s2.display();//注意这里不能直接写display(),而是要先写变量名
return 0;
}
运行结果:
Created constructor! //创建s1时,调用了默认构造函数
2 Alan //显示s2的类成员
Destroyed! //s1被销毁时,调用了一次析构函数
Destroyed! //同上
3.4.2 拷贝构造函数
1.定义:
一般用于变量初始化和拷贝相关代码。
2.自定义拷贝构造函数后,再次复制已经存在的对象时,不会调用(普通)构造函数,而是调用拷贝构造函数。
3.语法:
(1)必须带有类本身的常引用,否则就是普通构造函数。【小括号里变成被复制的实参对象】
类名(...const 类名& 对象名...){......}
#include <iostream>
using namespace std;
class CGirl
{
public:
string m_name;
int m_age;
//没有参数的普通构造函数
CGirl()
{
m_name.clear(); m_age = 0; cout << "Constructed!\n";
}
//没有重载的拷贝构造函数(默认拷贝构造函数)
CGirl(const CGirl& gg)
{
m_name = "beautiful " + gg.m_name; m_age =gg.m_age - 1; cout << "Copy constructed!\n";
}
//析构函数
~CGirl()
{
cout << "Destroyed!\n";
}
void show()
{
cout << "name: " << m_name << ",age: " << m_age << endl;
}
};
int main()
{
CGirl g1;
g1.m_name = "Alice"; g1.m_age = 23;
//CGirl g2 = g1;
CGirl g2(g1);
g2.show();
return 0;
}
(2)可以加参数重载,若类中加参数重载了拷贝构造函数而没有定义默认拷贝构造函数,编译器会提供默认的拷贝构造函数并使用:【只拷贝,也没有显示日志什么的】
//重载后的拷贝构造函数
CGirl(const CGirl& gg,int some)
{
m_name = "beautiful " + gg.m_name; m_age =gg.m_age - some;
cout << "Copy constructed!\n";
}
//使用默认构造函数,成功!而且显示使用的默认拷贝构造函数
int main()
{
CGirl g1;
g1.m_name = "Alice"; g1.m_age = 23;
CGirl g2(g1);
g2.show();
return 0;
}
(3)以值传递的方式调用函数时,若实参为对象,也会调用拷贝构造函数。【如func()函数】
void func(CGirl g)
{
g.show();
}//定义在class外面
int main()
{
CGirl g1;
g1.m_name = "Alice"; g1.m_age = 23;
func(g1);//这里也会用到拷贝构造函数
return 0;
}
(4)函数以值的方式返回对象时,可能会调用拷贝构造函数【有的编译器不会,如新版CPP和Linux,会直接使用之前的对象的地址,只是换个函数名继续用】
CGirl func()
{
CGirl gg;
gg.m_name = "Alice"; gg.m_age = 23;
cout << "The address of gg is" << &gg << endl;
return gg;
}
int main()
{
CGirl g = func();
g.show();
cout << "The address of g is" << &g << endl;
return 0;
}
4.浅拷贝/深拷贝
(1)浅拷贝
在使用拷贝构造函数时,只复制指向某个对象的指针,而不是复制对象本身【值】,新旧对象还是共享同一块内存。
危害1:改变一个对象的指针内存,会影响到另一个指针的内存。
危害2:在系统自动调用析构函数时,销毁了一个对象的指针,另一个指针也就成了野指针,无法再被调用。
这两种情况都是我们不愿意看到的。可能引起堆区数据混乱甚至系统崩溃。
#include <iostream>
using namespace std;
class CGirl
{
public:
string m_name;
int m_age;
int* m_ptr;//其他都只使用栈内存,而这里用了指针,使用堆内存!
//【但仍然是空指针,没有指向任何地址!不能解引用】
//没有参数的普通构造函数
CGirl()
{
m_name.clear(); m_age = 0;
m_ptr = NULL;
cout << "Constructed!\n";
}
//没有重载的拷贝构造函数(默认拷贝构造函数)
CGirl(const CGirl& gg)
{
m_name = gg.m_name; m_age =gg.m_age ;
m_ptr = gg.m_ptr;//问题在这,直接使用同一块内存!!!
cout << "Copy constructed!\n";
}
//析构函数
~CGirl()
{
//if (m_ptr == NULL)
//{
// delete m_ptr;//释放内存
// m_ptr = NULL;//不要指向原来的地址栏,防止你成为野指针。
//}
delete m_ptr;
m_ptr = NULL;//这句让删除后变成空指针而不是野指针了,空指针可以重复释放,它的地址指向虚无,不会被别人拿来用,所以会安全很多!
cout << "Destroyed!\n";
}
void show()
{
cout << "name: " << m_name << ",age: " << m_age <<
" m_ptr= " << m_ptr << " *m_ptr= " << *m_ptr << endl;
}
};
int main()
{
CGirl g1;
g1.m_name = "Alice"; g1.m_age = 23;
g1.m_ptr = new int(3);//给g1中的指针分配后,才能解引用
g1.show();
CGirl g2(g1);
*g2.m_ptr = 8;//这里g2改变了指针的内存,g1中的值也会改变
g1.show();
g2.show();
//调用析构函数销毁其中一个后,另一个就成了野指针,无法再被操作(释放内存)
return 0;
}
(2)深拷贝:
在使用拷贝构造函数进行拷贝已有对象时,重新分配一块内存给新对象,让大家各自操作自己的指针和内存。只需在拷贝构造函数中改动指针部分即可:
CGirl(const CGirl& gg)
{
m_name = gg.m_name; m_age = gg.m_age;
m_ptr = new int;
//*m_ptr = *gg.m_ptr;
memcpy(m_ptr, gg.m_ptr, sizeof(int));
cout << "Copy constructed!\n";
}
3.5 初始化列表
1.意义:
从概念上来讲,构造函数的执行可以分成两个阶段,初始化阶段和计算阶段,初始化阶段先于计算阶段,因此在初始化时,将基本工作做好。
有构造函数了为啥还需要初始化列表?直接带入更快捷!【区别于先创对象后传入】
2.初始化的形式:
类名(int a,int b):m_A(a),m_B(b)
{
}
【带入初值时,这初值可以是用系统的(默认无参数),也可以自定义(带参数,甚至可以带表达式,用加号连接)!】
ps:也可以在类外实现【类名::类名():】第二个类名表示初始化
#include <iostream>
using namespace std;
class CGirl {
public:
string m_name;
int m_age;
CGirl() :m_name("ALice"), m_age(23)
{
cout << "constructed by Initial line!" << endl;
}
CGirl(string name, int age) :m_name("strong "+name), m_age(age+6)
{
cout << "Constructed by Initial line with parameter!" << endl;;
}
void show()
{
cout << "name: " << m_name << " age: " << m_age << endl;
}
};
int main()
{
CGirl g1;//默认就使用无参数的初始化列表
g1.show();
CGirl g2("Bob", 12);//有参数就使用对应有参数的初始化列表
g2.show();
return 0;
}
3.不使用初始化列表的代码实现:
初始化列表与赋值有本质区别,若成员是类,使用初始化列表调用的是成员类的拷贝构造函数,而赋值则是先构造成员类的对象(会调用成员类的普通构造函数),然后再赋值。
用类创建对象,先初始化构造函数的形参对象(boy),然后再初始化类的成员(CGirl.m_boy)
#include <iostream>
using namespace std;
class CBoy
{
public:
string m_xm;
CBoy() {m_xm.clear(); cout << "Constructed CBoy()!\n" << endl;}
CBoy(string xm) { m_xm = xm; cout << "Constructed CBoy(string xm)!\n"; }
CBoy(const CBoy& bb) { m_xm = bb.m_xm; cout<<"Constructed CBoy(const CBoy & bb)!\n"; }
void show()
{
cout << "name: " << m_xm << endl;
}
};
class CGirl {
public:
string m_name;
int m_age;
CBoy m_boy;
CGirl()
{
cout << "constructed by Initial line!\n" << endl;
}
CGirl(string name, int age,CBoy boy) :m_name(name), m_age(age)
{//CGirl的name、age按照初始化的方式传入
m_boy.m_xm = boy.m_xm;//带入实值boy,让Girl里的m_boy被赋值
cout << "Constructed CGirl(name,age,boy)!\n" << endl;
}
void show()
{
cout << "name: " << m_name << " age: " << m_age
<<" boy: " <<m_boy.m_xm<< endl;
}
};
int main()
{
CBoy boy("王启舟");//声明一个CBoy类的boy,赋值采用 1.自带string的构造函数
//将boy作为实参带入之前,需要先调用 2.拷贝构造函数传入CGirl
//此外,超女类的m_boy也是类,需要使用 3.无参数的普通构造函数
CGirl girl("小豆",18,boy);//最后带入实参,使用 4.带参数的拷贝构造函数。
girl.show();
return 0;
}
ps:如果往CGirl传入实参boy时采用的引用符号,就不需要拷贝构造函数了。
【但是CGirl里的m_boy创建时还是需要普通构造函数!】
4.使用初始化列表的代码实现:
初始化列表是利用当前变量直接赋值一步到位,效率有所提升。代码实现如下:
CGirl(string name, int age,const CBoy &boy) :m_name(name), m_age(age),m_boy(boy)
{
cout << "Constructed CGirl(name,age,boy)!\n" << endl;
}
由此可知,使用初始化后,减少了CGirl自己用普通构造函数的过程,而是直接利用拷贝构造函数来带入即可。【初始化和赋值变成了一步操作】
5.注意
(1)若类的成员为常量或者引用,必须使用初始化列表【因为它俩只能在定义时初始化】
比如class CGirl{const int age,CBoy &m_boy},此时age和bb必须在类里使用初始化列表。
(2)若成员没有默认构造函数,则必须使用初始化列表。
3.6 const 修饰成员函数
#include <iostream> using namespace std; class Person { public: int m_A = 0; mutable int m_B = 0;//mutable变量不论在哪都能被修改 void show()const//常函数下的常变量不允许被修改 { //this->m_A = 100; this->m_B = 200; cout << m_A << endl; cout << m_B << endl; } }; int main() { Person p;//实例化 p.show(); const return 0; }
(1)被const修饰的函数或者成员无法修改值。
(2)mutable可以突破const限制,比如上式中show后面虽然有const,但是m_name变量有mutable,所以可以更改mutable的值。
(3)非const成员函数可以调用const成员函数和非const成员函数。【动态的谁都能调用】
(4)常对象和常函数只能调用常函数,连普通的成员函数也不能调用【静态的只能调用静态的,因为普通的成员函数是可以修改属性的】
3.7 this指针
1.意义:若类中的成员函数涉及多个对象,此时需要使用多个指针。
this指针的本质是指针常量,指针的指向是不可以修改的。【指向当前的类】
但是指针指向的值是可以更改的,如果想要变量值固定不变,需要在变量前加const/函数体后面加const
2.代码实现:
#include <iostream>
using namespace std;
class CGirl {
public:
mutable string m_name;
int m_age;
CGirl(const string& name, int age)
{
m_name = name; m_age = age;
}
const CGirl& pk(const CGirl& g)const
{
if (g.m_age > m_age)
return g;
else return *this;
}
void show()const
{
cout << "I'm " << m_name << " ,the oldest girl!" << endl;
}
};
//const CGirl& pk(const CGirl& gg1, const CGirl& gg2)
//{
// if (gg1.m_age < gg2.m_age)
// return gg2;
// else return gg1;
//}//C语言的写法,单独设计一个函数->C++:一切皆是对象,将其变为成员函数
int main()
{ //两个对象的比较:
//CGirl g1("Alice", 4), g2("Adare", 10);
//const CGirl& g3 = g2.pk(g1);
将g2和g1比较,结果附给p3;
//g3.show();
//多个对象的比较:
CGirl g1("Alice", 4), g2("Adare", 10),g3("Justin",13);
const CGirl& g = g3.pk(g2).pk(g1);
g.show();
return 0;
}
3.作用
每个成员函数(包括构造函数和析构函数)都有一个this指针,除了可以用它访问调用者对象的成员,还可以解决成员变量名和函数形参名相同的问题。
class Entity
{
int aa;
void func(int aa)
{
this->aa=aa;
}
}
3.8 静态成员
1.组成:类的静态成员包括静态成员变量和静态成员函数。
2.特点:多个对象之间的数据共享【同一份!】,比全局变量更具安全性。并不属于某一份对象。
3.如何定义:【类内声明,类外初始化】静态成员变量不会在创建对象时初始化,必须在程序的全局区用范围解析运算符::来初始化。
4.如何访问:由于编译阶段就分配内存。故:(1)可以通过类名来访问.【类名::静态成员】(2)也可以通过创建对象访问。
5.访问权限:公有权限下,类外可以访问。而私有权限下,类外无法访问。
#include <iostream>
using namespace std;
class CGirl {
public:
mutable string m_name;
static int m_age;
CGirl(const string& name, int age)
{
m_name = name; m_age = age;
}
void ShowName()
{
cout << "name: " << m_name << endl;
}
static void ShowAge()
{
cout << "age:" << m_age << endl;
}
};
int CGirl::m_age=8;//初始化类的静态成员变量
int main()
{
CGirl::ShowAge();
cout << "CGirl::m_age:" << CGirl::m_age << endl;
//不是使用类的成员变量函数ShowAge,而是不创建对象直接显示输出
CGirl g1("Alice", 23);
CGirl g2("Bob", 24);
CGirl g3("Cab", 25);
g1.ShowAge();
g1.ShowName();
g2.ShowAge();
g2.ShowName();
g3.ShowAge();
g3.ShowName();
return 0;
}
运行结果:打印静态变量初始值,再被赋值、赋值、赋值,最后age停在了25,再打印。
3.9 友元
1.作用:
让类外的一些函数或者类可以有权限访问私有属性。
2.关键字:friend
3.实现方法:
全局函数做友元/类做友元/成员函数做友元。
(1)全局函数做友元
#include <iostream>
using namespace std;
#include <string>
class Building
{
friend void goodGay(Building* building);
public:
Building() //初始化
{
this->m_SettingRoom = "客厅";
this->m_BedRoom = "卧室";
}
public:
string m_SettingRoom;
private:
string m_BedRoom;
};
void goodGay(Building* building)//这里要填地址,主函数中就需要用取址符
{
cout << "好基友正在访问: " << building->m_SettingRoom << endl;
cout << "好基友正在访问: " << building->m_BedRoom << endl;
}
void test01()
{
Building building;
goodGay(&building);
}
int main()
{
test01();
return 0;
}
(2)类做友元
#include <iostream>
using namespace std;
#include <string>
class Building
{
friend class GoodGay;//整个类都可以进入private处
public:
Building();//可以用初始化列表,也可以普通构造函数
public:
string m_SettingRoom;
private:
string m_BedRoom;
};
//Building::Building() :m_SettingRoom("客厅"), m_BedRoom("卧室") {};//这里使用了类外初始化链表,也可以类内实现
Building::Building()
{
this->m_SettingRoom = "客厅";
this->m_BedRoom = "卧室";
};
class GoodGay
{
public:
GoodGay();//普通构造函数
void visit();//visit知道building指针,即地址,但是能否进入得看GoodGay是否为building的友元
private:
Building* building;
};
GoodGay::GoodGay()
{
//创建建筑对象给building
building = new Building;
}
void GoodGay::visit()
{
cout << "好基友正在访问:" << building->m_SettingRoom << endl;
cout << "好基友正在访问:" << building->m_BedRoom << endl;
}
void test01()
{
GoodGay gg;
gg.visit();
}
int main()
{
test01();
return 0;
}
(3)成员函数做友元
#include <iostream>
using namespace std;
#include <string>
class GoodGay
{
public:
GoodGay();//普通构造函数
void visit01();//让GoodGay下的visit函数可以访问Building里的私有空间
void visit02();
private:
Building* building;
};
class Building
{
friend void GoodGay::visit01();
public:
Building();//可以用普通构造函数,也可以初始化列表
public:
string m_SettingRoom;
private:
string m_BedRoom;
};
//Building::Building() :m_SettingRoom("客厅"), m_BedRoom("卧室") {};//这里使用了类外初始化链表,也可以类内实现
Building::Building()
{
this->m_SettingRoom = "客厅";
this->m_BedRoom = "卧室";
};
GoodGay::GoodGay()//构造函数没有返回值
{
//创建建筑对象给building
building = new Building;
}
void GoodGay::visit01()
{
cout << "好基友正在访问:" << building->m_SettingRoom << endl;
cout << "好基友正在访问:" << building->m_BedRoom << endl;
}
void GoodGay::visit02()
{
cout << "好基友正在访问:" << building->m_SettingRoom << endl;
//cout << "好基友正在访问:" << building->m_BedRoom << endl;
}
void test01()
{
GoodGay gg;
gg.visit01();
gg.visit02();
}
int main()
{
test01();
return 0;
}
1.无法运行
2.Building* building;的意义是创建新房子,并把地址给GoodGay!
3.10 运算符重载、函数重载【自定义】
(1)加号运算符重载
对于自定义的数据类型,编译器不知道怎么算,所以这里要用到运算符重载。如两个Person类相加(p1+p2)
办法:要么使用成员函数重载,要么用全局函数
a.通过成员函数重载【只需再传入一个新变量】
#include <iostream>
using namespace std;
#include <string>
class Person
{
public:
int m_A;
int m_B;
Person& operator+(Person& p)//operator+是系统取的通用名称,一个返回值为Person的重载加法!名叫【operator+】
{
Person temp;
temp.m_A = this->m_A + p.m_A;
temp.m_B = this->m_B + p.m_B;
return temp;
}
};
void test01()
{
Person p1;
p1.m_A = 10;
p1.m_B = 10;
Person p2;
p2.m_A = 10;
p2.m_B = 10;
//Person p3 = p1.operator+(p2);
Person p3 = p1 + p2;//这里实际上是上一行的简化写法
cout << "p3.m_A=" << p3.m_A << endl;
cout << "p3.m_B=" << p3.m_B << endl;
}
int main()
{
test01();
return 0;
}
b.全局函数重载+号【需要传入两个变量】
Person& operator+(Person& p1,Person& p2)
{
Person temp;
temp.m_A = p1.m_A + p2.m_A;
temp.m_B = p1.m_B + p2.m_B;
return temp;
}
Person p3=operator+(p1,p2)//实质上就是调用了全局函数operator+
注意:
对于内置的数据类型(如int,float等)表达式的运算符是无法改变的。
(2)左移运算符重载
无法用成员函数:因为p.operator(cout),此时p永远在左侧,无法实现cout<<p【p在右侧】
所以采用全局函数【operator<<】来实现
只要<<检测到了输出一个类,就会调用这个函数
class Person
{
friend ostream& operator<< (ostream& cout, Person& p);
public:
Person(int a, int b)//构造函数
{
m_A = a;
m_B = b;
}
private:
int m_A;
int m_B;
};
void test()
{
Person p(10, 10);
cout << p << " Helloworld " << endl;
}
ostream &operator<< (ostream& cout, Person& p)//这里把"<<"变成"cout<<p",并返回cout的地址
//全局变量cout不论是输入还是返回都需要加&
{
cout << "m_A= " << p.m_A << " m_B= " << p.m_B;
return cout;
}
int main()
{
test();
return 0;
}
ps:this
#include "iostream"
using namespace std;
class Person
{
public:
Person(int age);
Person& PersonAddAge(Person& p)
{ this->age += p.age;
return *this; }
void show()
{
cout << age << endl;
}
private:
int age;
};
Person::Person(int age)
{
this->age = age;
}
int main()
{
Person p1(10);
Person p2 = p1.PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1);
p1.show();
p2.show();
return 0;
}
运算结果 80,80
(3)复数运算符重载
#include <iostream>
using namespace std;
class complex{
public:
complex(double real = 0.0, double imag = 0.0): m_real(real), m_imag(imag){ };
public:
friend complex operator+(const complex & A, const complex & B);
friend complex operator-(const complex & A, const complex & B);
friend complex operator*(const complex & A, const complex & B);
friend complex operator/(const complex & A, const complex & B);
friend istream & operator>>(istream & in, complex & A);
friend ostream & operator<<(ostream & out, complex & A);
private:
double m_real; //实部
double m_imag; //虚部
};
//重载加法运算符
complex operator+(const complex & A, const complex &B){
complex C;
C.m_real = A.m_real + B.m_real;
C.m_imag = A.m_imag + B.m_imag;
return C;
}
//重载减法运算符
complex operator-(const complex & A, const complex &B){
complex C;
C.m_real = A.m_real - B.m_real;
C.m_imag = A.m_imag - B.m_imag;
return C;
}
//重载乘法运算符
complex operator*(const complex & A, const complex &B){
complex C;
C.m_real = A.m_real * B.m_real - A.m_imag * B.m_imag;
C.m_imag = A.m_imag * B.m_real + A.m_real * B.m_imag;
return C;
}
//重载除法运算符
complex operator/(const complex & A, const complex & B){
complex C;
double square = A.m_real * A.m_real + A.m_imag * A.m_imag;
C.m_real = (A.m_real * B.m_real + A.m_imag * B.m_imag)/square;
C.m_imag = (A.m_imag * B.m_real - A.m_real * B.m_imag)/square;
return C;
}
//重载输入运算符
istream & operator>>(istream & in, complex & A){
in >> A.m_real >> A.m_imag;
return in;
}
//重载输出运算符
ostream & operator<<(ostream & out, complex & A){
out << A.m_real <<" + "<< A.m_imag <<" i ";;
return out;
}
int main(){
complex c1, c2, c3;
cin>>c1>>c2;
c3 = c1 + c2;
cout<<"c1 + c2 = "<<c3<<endl;
c3 = c1 - c2;
cout<<"c1 - c2 = "<<c3<<endl;
c3 = c1 * c2;
cout<<"c1 * c2 = "<<c3<<endl;
c3 = c1 / c2;
cout<<"c1 / c2 = "<<c3<<endl;
return 0;
}
(4)递增运算符重载
class MyInteger
{
friend ostream& operator<<(ostream& cout, MyInteger myint);
public:
MyInteger()
{
m_Num = 0;
}
//前置递增重载
MyInteger& operator++()
{
this->m_Num++;
//也可以直接写成m_Num++
return *this;
}
//后置递增重载,先记录当前值,再++,最后输出记录的值【所以这里返回的值,而不是*this】
// MyInteger operator++(int) int用于区分前置和后置的函数重载
MyInteger operator++(int)
{
MyInteger temp = *this;
m_Num++;
return temp;
}
private:
int m_Num;
};
void test01()
{
MyInteger myint;
cout << ++myint << endl;//myint=1,输出1,
cout << myint << endl;//输出myint=1
cout << myint++ << endl;//输出1,myint=2
cout << myint << endl; //输出myint = 2
}
//输出MyInteger里的整型数据,注意要用友元
ostream& operator<<(ostream& cout,MyInteger myint)
{
cout << myint.m_Num << endl;
return cout;
}
int main()
{
test01();
return 0;
}
1.为啥不要参数,参数需要与否如何定义?->这里主要是区分前置和后置递增。
2.返回值为啥是 *this不是this?->返回类本身,而不是返回指向它的指针。