类和对象-02
1. this 指针
1.1 概念:
- 谁调用this所在的函数,this就存储谁的地址,即指向谁 。
1.2 特点:
- 在当前类的非静态成员函数中调用本类非静态成员时,默认有this关键字
- 静态成员函数,没有this指针。
1.3 使用场景
- 局部变量与成员变量重名时,使用this区分
- 调用本类其他成员,此时this可以省略不写
1.4 示例
#include <iostream>
#include<cstring>
using namespace std;
class A{
private:
int x;
public:
A(){}
A(int a):x(a){}
void test01()
{
cout << x << endl;
}
void test02();
};
void A::test02()
{
//谁调用this所在的函数,this就指向谁
//2,在本类函数中调用本类其他成员,此时可以忽略this不写,此时下面两行代码相等
//cout << this->x << endl;
cout << x << endl;
}
class Person{
private:
char name[50];
int age;
public:
Person(char *name,int age)
{
//1,当局部变量与成员变量重名时,用于区分
strcpy(this->name,name);
this->age = age;
}
static void test()
{
//静态函数中没有this关键字,假如有this,用类名调用静态函数时是没有指向的
//静态函数中也不能使用this关键字
//cout << this->name << endl;
}
};
int main(int argc, char *argv[])
{
A a(10);
a.test02(); //10
A a2(20);
a2.test02(); //20
return 0;
}
2. const修饰成员函数(了解)
概念:
const修饰的成员函数内部不能对成员数据写操作,mutable修饰的成员数据 除外。
示例:
#include <iostream>
#include<cstring>
using namespace std;
class B{
private:
int x;
mutable int a;
public:
B(){
}
B(int x){
this->x = x;
}
void test() const
{
//const修饰的成员函数中,不能修改该类的其他成员变量的值
//x = 10;
// 可以读取该类的成员变量的值
cout << x << endl;
//const修饰的函数中可以修改mutable修饰的成员变量的值
a = 100;
}
};
int main(int argc, char *argv[])
{
B b(1);
b.test();
return 0;
}
3. 友元(重要)
3.1 概述
关键字:friend
可以声明:
- 全局函数
- 成员函数
- 类
注意:
- 友元打破c++的封装性。一般用于运算符重载
3.2 全局友元函数
特点:
- 可以访问其友元类的任意成员,包括私有成员
步骤:
- 定义并实例全局函数
- 在
类中声明
步骤1中的函数为友元函数(这个函数是哪个类的友元函数,就在哪个类中声明)- 步骤1中定义的函数,可以访问步骤2中定义的类中的所有成员
示例:
#include <iostream>
using namespace std;
class A{
//声明全局函数fun01为A类的友元函数
friend void fun01();
private:
int x;
public:
A(int x)
{
this->x = x;
}
};
void fun01()
{
A a(10);
//访问A类中的x
cout << a.x << endl;
}
int main(int argc, char *argv[])
{
fun01(); //10
return 0;
}
3.3 成员友元函数
特点:
- 可以访问其友元类的任意成员,包括私有成员
步骤:
- 定义B类,其中函数只定义不实现
- 定义并实现A类,在其中声明B类的成员函数testB为友元类
- 实现B类的成员函数,其中可以访问A的任意成员
注意:
- 成员函数作为友元 那么成员函数所在的类 必须定义到最上方
- 成员函数所在的类的所有成员函数 必须在两个类的下方实现
示例:
#include <iostream>
using namespace std;
//定义B类,其中函数只定义不实现
class B{
public:
void testB();
};
//定义并实现A类,在其中声明成员函数testB为友元函数
class A{
//声明B类中的成员函数testB为A类的友元函数
friend void B::testB();
private:
int x;
public:
A(int x)
{
this->x = x;
}
};
//实现B类的成员函数
void B::testB()
{
A a(10);
cout << a.x << endl;
}
int main(int argc, char *argv[])
{
B b;
b.testB(); //10
return 0;
}
3.4 整个类作为友元
特点:
在B中声明A为B的友元类,此时A中任意成员函数中皆可直接访问B中的成员
步骤:
- 定义C类
- 定义并实现A类,在其中声明C类为
友元类
- 实现C类,C类中所有函数都是A类的友元函数,可以访问A类中的任意成员
示例:
#include <iostream>
using namespace std;
//声明C类
class C;
//定义并实现A类,在其中声明成员函数testB为友元函数
class A{
//声明C类为A类的友元类
//此时C类中所有函数都是A类的友元函数
friend class C;
//友元函数中可以访问其对应的友元类中的任意成员,包含私有成员
private:
int x;
public:
A(int x)
{
this->x = x;
}
};
class C{
public:
void testC01(A& a){
cout << a.x << endl;
}
void testC02(A& a){
cout << a.x << endl;
}
};
int main(int argc, char *argv[])
{
C c;
A a(20);
c.testC01(a); //20
A a2(100);
c.testC02(a2); //100
return 0;
}
3.5 注意
- 友元关系 不能被继承。
- 友元关系 是单向的,类 A 是类 B 的朋友,但类 B 不一定是类 A 的朋友。
- 友元关系 不具有传递性。类 B 是类 A 的朋友,类 C 是类 B 的朋友,但类 C 不一定是类A的朋友
4. string
作用:c++字符串类
,使其字符串操作方便
可以当做数据类型来用。
示例:
#include <iostream>
#include <string>
#include <cstring>
using namespace std;
int main(int argc, char *argv[])
{
//c语言定义字符串的方式
//char str01[50] = "hello world";
//char *str02 = "hello world";
string str01 = "hello world";
//1、输出字符串
cout << str01 << endl; //hello world
// string str02;
// //2、输入字符串
// cin >> str02;
// cout << str02 << endl;
//3、字符串赋值,使用深拷贝
string str03 = str01;
cout << "str03:" << str03 << endl; //str03:hello world
str01 = "hello";
cout << "str03:" << str03 << endl; //str03:hello world
// c拼接字符串
// char str11[50] = "hello";
// char str22[10] = "world";
// strcat(str11,str22);
// cout << str11 << endl;
string str04 = "hello";
string str05 = "world";
//4、c++字符串拼接
str04 = str04 + str05;
cout << "str04:" <<str04 << endl; //str04:helloworld
//c比较字符串是否相同
//0,相同
//非0不同
// char str11[50] = "hello";
// char str22[10] = "world";
// int i = strcmp(str11,str22);
// cout << "i =" << i << endl;
//5、c++字符串比较
//返回值bool型
//0,false; 1,true
cout << (str04 == str05) << endl; //0
string str06 = "helloworld";
cout << (str04 == str06) << endl; //1
return 0;
}
5. 运算符重载
5.1 引入
经源码查看string发现其也是一个类
那么为什么string的类对象可以使用>>,<<,+,==等运算符,我们自定义的类不行呢?
因为string类对运算符进行了重载
那我们如何实现运算符的重载?
5.2 概述
作用:是对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型。
关键字:operator
语法:
返回值类型 operator 运算符(形参列表)
{
函数体
}
如:
>>
void operator >>(形参列表)
{ }
思路:
1、分析 运算符的运算对象的个数
2、分析
运算符左边
的运算对象
是自定对象
还是其他
- 左边:是 其他 只能全局函数实现
- 左边:自定义对象 (必须使用友元)
- 可以用使用 **全局函数 ** 重载运算符(参数个数 和 运算符对象的个数一致)
- 也可以使用 成员函数 重载运算符(参数可以少一个) (推荐)
可以重载的运算符:
注意:尽量不要重载&& ||,无法实现&& || 短路 。
5.3 重载<<,>>运算符
效果:
通过<<输出自定义类型的变量
或
通过>>输入自定义类型变量
分析:
<< 、>>符号左边为
cout
或cin
不是自定义对象
,只能使用全局函数
对其进行重载
示例:
#include <iostream>
#include <cstring>
#include <string>
using namespace std;
class Stu{
//输出、输入全局友元函数声明
friend ostream& operator <<(ostream& out,Stu& stu);
friend istream& operator >>(istream& in,Stu& stu);
private:
char name[50];
char sex[10];
int age;
public:
Stu(){}
Stu(char *name,char *sex,int age)
{
strcpy(this->name,name);
strcpy(this->sex,sex);
this->age = age;
}
Stu(const Stu& stu)
{
strcpy(this->name,stu.name);
strcpy(this->sex,stu.sex);
this->age = stu.age;
}
};
//1参:符号左边的变量
//2参:符号右边的变量
//参数传递时防止拷贝构造,取别名
ostream& operator <<(ostream& out,Stu& stu)
{
//函数里面直接使用传进来的参数打印
out << stu.name << endl;
out << stu.sex << endl;
out << stu.age << endl;
out << "-------------------" << endl;
return out;
}
//>> 输入重载
istream& operator >>(istream& in,Stu& stu)
{
in >> stu.name >> stu.sex >> stu.age;
return in;
}
void fun01()
{
string str01 = "abc";
cout << str01 << endl;
Stu stu("张三","男",19);
Stu stu02("李四","男",19);
cout << stu << stu02 << endl;
}
void fun02()
{
Stu stu;
cin >> stu;
cout << stu;
}
int main(int argc, char *argv[])
{
fun01();
fun02();
return 0;
}
5.4 重载 + 运算符
效果:
使用+运算符将 自定义类型 对象的属性一一相加
分析:
+
符号左边为自定义类型
,可以使用全局函数重载
也可以使用成员函数重载
。
示例:
#include <iostream>
#include <cstring>
#include <string>
using namespace std;
class Stu{
private:
char name[50];
char sex[10];
int age;
public:
Stu(){}
Stu(char *name,char *sex,int age)
{
strcpy(this->name,name);
strcpy(this->sex,sex);
this->age = age;
}
Stu(const Stu& stu)
{
strcpy(this->name,stu.name);
strcpy(this->sex,stu.sex);
this->age = stu.age;
}
//调用该函数的对象就是符号左边的变量
//参数符号右边的变量
Stu& operator +(Stu& stu)
{
Stu *s = new Stu();
//置0,新创建的是随机值,下面会乱码
memset(s->name,0,50);
strcat(s->name,this->name);
strcat(s->name,stu.name);
s->age = this->age + stu.age;
//想返回的是Stu的引用个,而不是指针,所以是*s,
//给s取值就是指针指向那片内存的地址,即Stu()这个对象
return *s;
}
};
void fun03()
{
Stu s01("德玛","男",18);
Stu s02("西亚","男",20);
Stu s03 = s01 + s02; //德玛西亚
cout << s03 << endl; //38
}
int main(int argc, char *argv[])
{
fun03();
return 0;
}
5.5 重载 == 运算符
效果:
比较类中成员变量值是否相同
分析:
符号左边为
自定义类型
,可以使用全局函数重载也可以使用成员函数重载
示例:
#include <iostream>
#include <cstring>
#include <string>
using namespace std;
class Stu{
private:
char name[50];
char sex[10];
int age;
public:
Stu(){}
Stu(char *name,char *sex,int age)
{
strcpy(this->name,name);
strcpy(this->sex,sex);
this->age = age;
}
Stu(const Stu& stu)
{
strcpy(this->name,stu.name);
strcpy(this->sex,stu.sex);
this->age = stu.age;
}
int operator ==(Stu& stu)
{
int x = strcmp(this->name,stu.name);
int y = strcmp(this->sex,stu.sex);
int z = this->age - stu.age;
if(x == 0 && y == 0 && z == 0)
{
return 1;
}
else
{
return 0;
}
}
};
void fun04()
{
Stu s01("德玛","男",18);
Stu s02("西亚","男",20);
Stu s03("西亚","男",20);
//s01 == s02
//如果s01对象的属性值与s02的属性值,相同返回1,不同返回0
cout << (s01 == s02) << endl; //0
cout << (s03 == s02) << endl; //1
}
int main(int argc, char *argv[])
{
fun04();
return 0;
}
5.6 重载 ++ 运算符
注意:
++运算符分为:
- ++在前,先自增在运算
- ++在后,先运算在自增
所以需要重载两种
分析:
当编译器看到
++a(前置++)
,它就调用 operator++(Type& a)(全局函数),operator++()(成员函数)当编译器看到
a++(后置++)
,它就会去调用 operator++(Type& a,int)(全局函数),operator++(int)(成员函数)
示例:
#include <iostream>
#include <cstring>
#include <string>
using namespace std;
class Stu{
private:
char name[50];
char sex[10];
int age;
public:
Stu(){}
Stu(char *name,char *sex,int age)
{
strcpy(this->name,name);
strcpy(this->sex,sex);
this->age = age;
}
Stu(const Stu& stu)
{
strcpy(this->name,stu.name);
strcpy(this->sex,stu.sex);
this->age = stu.age;
}
void operator ++()
{
//cout << "成员函数重载++前置" << endl;
this->age++;
}
Stu& operator ++(int)
{
//cout << "成员函数重载++后置" << endl;
Stu *old = new Stu(this->name,this->sex,this->age);
this->age++;
return *old;
}
};
//全局函数
//void operator ++(Stu& stu)
//{
// cout << "++前置调用" << endl;
//}
//void operator ++(Stu& stu,int)
//{
// cout << "++后置调用" << endl;
//}
int main(int argc, char *argv[])
{
Stu s01("德玛","男",18);
++s01;
cout << s01;
//先运算再自增,所以年龄还是原值
Stu s02 = s01++;
cout << s02 << endl;
cout << s01 << endl;
delete &s02;
return 0;
}
结果:
5.7 重载 ->与* 运算符
效果:
重载指针运算符, 实现智能指针
示例:
- 代码推演:
#include <iostream>
using namespace std;
class A
{
private:
int x;
public:
A()
{
cout << "A的无参构造函数被调用了" << endl;
}
A(int x)
{
this->x = x;
cout << "A的有参构造函数被调用了" << endl;
}
A(const A& a)
{
this->x = a.x;
cout << "A的拷贝构造函数被调用了" << endl;
}
~A()
{
cout << "A的析构函数被调用了" << endl;
}
void setX(int x)
{
this->x = x;
}
int getX()
{
return x;
}
};
void fun01()
{
A *a = new A(10); //A的有参构造函数被调用了
}
int main(int argc, char *argv[])
{
fun01();
return 0;
}
观察以上代码,我们发现创建的对象没有被销毁,但是我们在编写代码时经常会忘记销毁,那该怎么办呢?
解决方案如下
#include <iostream>
using namespace std;
class A{
private:
int x;
public:
A()
{
cout << "A的无参构造函数被调用了" << endl;
}
A(int x)
{
this->x = x;
cout << "A的有参构造函数被调用了" << endl;
}
A(const A& a)
{
this->x = a.x;
cout << "A的拷贝构造函数被调用了" << endl;
}
~A()
{
cout << "A的析构函数被调用了" << endl;
}
void setX(int x)
{
this->x = x;
}
int getX()
{
return x;
}
};
//一个类作为一个类的属性,
//当外边这个类对象释放的时候,里面的类对象也会被释放
//所以可以在外边这个类的析构函数中,做释放的操作。
class FreeA{
private:
//A类的对象作为属性,因为A的对象在创建时,使用的是new,指针接收
//所以此处也用指针作为属性
A* a;
public:
//构造函数,包裹A的对象
FreeA(A *a)
{
this->a = a;
}
~FreeA()
{
//a为指针变量,需要释放,delete
delete a;
}
//重载 ->
A* operator ->()
{
return a;
}
//重载*
A& operator *()
{
return *a;
}
};
void fun01()
{
// A *a = new A(10);
// a->get_data();
FreeA a1(new A(10));
//要通过创建的对象获取或者修改 A的对象的值,即10,就得重载运算符 -> *
// a1 -> get_data();就是要 a1->的结果 能得到 A的对象的指针,就可以调用get_data()
// cout << a1 -> get_data() << endl;
//或者 (*a1).get_data(); 指针a 取值(*a) 就是A的对象,直接.get_data();
cout << (*a1).get_data() << endl;
}
int main(int argc, char *argv[])
{
fun01();
return 0;
}
结果:
5.8 重载 () 运算符
作用:类 对象作为函数调用
返回值类型 operator()(参数列表)函数。
对象作为函数调用
对象名(实参列表);
一种仿函数
示例:
#include <iostream>
using namespace std;
//重载()运算符,可以使其对象作为函数进行调用
class A{
public:
void operator()()
{
cout << "函数被调用" << endl;
}
};
void fun01()
{
A a;
a();
//隐式创建调用的就是()运算符
}
int main(int argc, char *argv[])
{
fun01();
return 0;
}
//函数被调用
5.9 重载 = 运算符
注意:
=重载时,可能会调用类本身的拷贝构造函数。
如果左值是没有创建的对象时,会调用拷贝构造函数.
如果左值是已创建的类对象,会执行 =重载函数,实现数据的拷贝。
示例:
#include <iostream>
using namespace std;
class B{
public:
B()
{
cout << "构造函数" << endl;
}
B(const B& b)
{
cout << "拷贝构造" << endl;
}
void operator=(B& b)
{
cout << "重载的=号运算符" << endl;
}
};
void fun02()
{
B b1;
//初始化时,调用拷贝构造
B b2 = b1;
//初始化后调用重载的=号运算符
b2 = b1;
}
int main(int argc, char *argv[])
{
fun02();
return 0;
}
//构造函数
//拷贝构造
//重载的=号运算符