您的位置:首页 > 编程语言 > C语言/C++

C++学习笔记(八)(C++对象模型和this指针 ,友元)

2020-03-06 18:58 891 查看

本笔记主要来源于教程https://www.bilibili.com/video/av41559729?p=1

4.3 C++对象模型和this指针

4.3.1 成员变量和成员函数分开储存

在C++中,类内的成员变量和成员函数分开存储
只有非静态成员变量才属于类的对象上

//成员变量和成员函数分开存储的

class Person
{
int m_A;//非静态成员变量  属于类的对象上

static int m_B;//静态成员变量   不属于类的对象上

void func()//非静态成员函数  不属于类的对象上
{
}
//int m_C;
static void func2()//静态成员函数  不属于类的对象上
{
}

};
int Person::m_B = 0;
void test01()
{
Person p;
//空对象占用的内存空间为:1
//C++编译器会给每个空对象也分配一个字节空间,是为了区分空对象占内存的位置
//每个空对象也应该有一个独一无二的内存地址
cout << "size of p= " << sizeof(p) << endl;

}
void test02()
{
Person p;
cout << "size of p= " << sizeof(p) << endl;

}

int main()
{
//test01();
test02();

system("pause");
return 0;

}

4.3.2 this指针概念

通过4.3.1我们知道在C++中成员变量和成员函数是分开存储的
每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会共用一块代码。
那么问题是:这一块代码是如何区分哪个对象调用自己的呢?

C++通过提供特殊的对象指针,this指针,解决上述问题,this指针指向被调用的成员函数所属的对象

this指针是隐含每一个非静态成员函数内的一种指针
this指针不需要定义,直接使用即可。

this指针的用途:

  • 当形参和成员变量同名时,可用this指针来区分
  • 在类的非静态成员函数中返回对象本身,可使用return *this
class Person
{
public:
Person(int age)
{
//this指针指向被调用的成员函数所属的对象
this->age = age;
}

Person& PersonAddAge(Person &p)//如果不返回引用而返回值 Person,会调用拷贝函数,创建新对象,结果仍为20
{
this->age += p.age;

//this指向p2的指针,而*this指向的就是p2这个对象本体
return *this;

}

int age;

};

//1、解决名称冲突
void test01()
{
Person p1(18);
cout << "p1的年龄为: " << p1.age << endl;

}

//2、返回对象本身用*this
void test02()
{
Person p1(10);

Person p2(10);

//链式编程思想
p2.PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1);

cout << "p2的年龄为: " << p2.age << endl;

}
int main()
{
//test01();
test02();

system("pause");
return 0;

}

4.3.3 空指针访问成员函数

C++中空指针也是可以调用成员函数的,但是也要注意有没有用到this指针

如果用到this指针,需要加以判断保证代码的健壮性。

//空指针调用成员函数

class Person
{
public:

void showClassName()
{
cout << "this is Person class" << endl;

}
void showPersonAge()
{
//报错原因是因为传入的指针为NULL
if (this == NULL)
{
return;//避免this的坑 提高代码健壮性
}

cout << "age= " << m_Age << endl;//相当于this->m_Age

}
int m_Age;
};
void test01()
{
Person *p = NULL;

p->showClassName();

p->showPersonAge();//报错
}

int main()
{
test01();

system("pause");
return 0;

}

4.3.4 const修饰成员函数

常函数:

  • 成员函数后加const我们称这个函数为常函数
  • 常函数内不可以修改成员属性
  • 成员属性声明时加关键字mutable后,在常函数中仍然可以修改

常对象

  • 声明对象前加const称该对象为常对象
  • 常对象只能调用常函数
    示例:
//常函数
class Person
{
public:
//this指针的本质是  指针常量  指针的指向是不可以修改的
//Person *const this;
//在成员函数后面加const,修饰的是this指针,让指针指向的值也不可以修改 const Person *const this;
void showPerson() const
{
this->m_B = 100;
//this->m_A = 100;
//this = NULL;//this指针是不可以修改指向的

}
void func()
{
m_A = 100;
}
int m_A;
mutable int m_B;//特殊变量,即使在常函数中,也可以修改这个值,加关键字mutable
};
void test01()
{
Person p;
p.showPerson();
}

//常对象
void test02()
{
const Person p;//在对象前加const,变为常对象
//p.m_A = 100;//报错
p.m_B = 100;//m_B是一个特殊值,在常对象下也可以修改

//常对象只能调用常函数
p.showPerson();
p.func();//报错,常对象不可以调用普通成员函数,因为普通成员函数可以修改属性
}
int main()
{

system("pause");
return 0;

}

4.4 友元

生活中你的家有客厅(Public),有你的卧室(Private)
客厅所有来的客人都可以进去,但是你的卧室是私有的,也就是说只有你能进去
但是呢,你也可以允许你的好闺蜜好基友进去

在程序里,有些私有属性也想让类外特殊的一些函数或者类进行访问,就需要用到友元的技术

友元的目的就是让一个函数或者类,访问另一个类中私有成员

友元的关键字为friend
友元的三种实现:

  • 全局函数做友元
  • 类做友元
  • 成员函数做友元

4.4.1 全局函数做友元

//建筑物类
class Building
{
//goodGay全局函数是 Building好朋友,可以访问Building中私有成员
friend void goodGay(Building *building);
public:
Building()
{
m_SittingRoom = "客厅";
m_BedRoom = "卧室";
}

public:
string m_SittingRoom;//客厅

private:
string m_BedRoom;

};
//全局函数
void goodGay(Building *building)
{
cout << "好基友全局函数 正在访问" << building->m_SittingRoom << endl;

cout << "好基友全局函数 正在访问" << building->m_BedRoom<< endl;

}
void test01()
{
Building building;
goodGay(&building);
}
int main()
{
test01();

system("pause");
return 0;

}

4.4.2 类做友元

//类做友元

class Building;
class GoodGay
{
public:
GoodGay();
void visit();//参观函数,访问Building中的属性

Building *building;

};
class Building
{
//GoodGay类是本类的好朋友,可以访问本类中私有的成员
friend class GoodGay;
public:
Building();
public:
string m_SittingRoom;//客厅

private:
string m_Bedroom;//卧室

};
//类外写成员函数
Building::Building()
{
m_SittingRoom = "客厅";
m_Bedroom = "卧室";
}

GoodGay::GoodGay()
{
//创建一个建筑物对象
building = new Building;

}

void GoodGay::visit()
{
cout << "好基友类正在访问: " << building->m_SittingRoom << endl;
cout << "好基友类正在访问: " << building->m_Bedroom<< endl;
}
void test01()
{
GoodGay gg;
gg.visit();
}
int main()
{
test01();

system("pause");
return 0;

}

成员函数做友元

class Building;
class GoodGay
{
public:
GoodGay();

void visit();//让visit函数可以访问Building中私有成员
void visit2();//让visit2函数不可以访问Building中私有成员

Building *building;

};
class Building
{
//告诉编译器  GoodGay类下的visit成员函数作为本类的好朋友,可以访问私有成员
friend void GoodGay::visit();
public:
Building();

public:
string m_SettingRoom;//客厅

private:
string m_BedRoom;//卧室

};
//类外实现成员函数
Building::Building()
{
m_SettingRoom = "客厅";
m_BedRoom = "卧室";

}
GoodGay::GoodGay()
{
building = new Building;
}
void GoodGay::visit()
{
cout << "visit函数正在访问:" << building->m_SettingRoom << endl;

cout << "visit函数正在访问:" << building->m_BedRoom << endl;

}

void GoodGay::visit2()
{
cout << "visit2函数正在访问:" << building->m_SettingRoom << endl;

//cout << "visit2函数正在访问:" << building->m_BedRoom << endl;
}
void test01()
{
GoodGay gg;
gg.visit();
gg.visit2();
}
int main()
{
test01();

system("pause");
return 0;

}

4.5 运算符重载

运算符重载概念:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型

4.5.1 加号运算符重载

作用:实现两个自定义数据类型相加的运算

//加号运算符重载
class Person
{
public:
//1、成员函数重载+号
Person operator+(Person &p)
{
Person temp;
temp.m_A = this->m_A + p.m_A;
temp.m_B = this->m_B + p.m_B;
return temp;

}
int m_A;
int m_B;
};
//2、全局函数重载+号
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 operator+(Person &p1, int num)
{
Person temp;
temp.m_A = p1.m_A + num;
temp.m_B = p1.m_B + num;
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 = operator+(p1, p2);

Person p3 = p1 + p2;
//运算符重载 也可以发生函数重载

Person p4 = p1 + 100;//Person+int

cout << "p3.m_A= " << p3.m_A << endl;
cout << "p3.m_B= " << p3.m_B<< endl;

cout << "p4.m_A= " << p4.m_A << endl;
cout << "p4.m_B= " << p4.m_B << endl;
}

int main()
{
test01();

system("pause");
return 0;

}
总结1:对于内置的数据类型的表达式的运算符是不可能改变的 总结2:不要滥用运算符重载

4.5.2 左移运算符重载

作用:可以输出自定义数据类型

//左移运算符重载
class Person
{
public:
Person(int a, int b)
{
m_A = a;
m_B = b;
}
friend ostream &operator<<(ostream &cout, Person &p);
private:
//利用成员函数重载左移运算符 p.operator<<(cout) 简化版本p << cout
//不会利用成员函数重载<<运算符,因为无法实现cout在左侧
/*void operator<<(Person &p)
{
}
*/
int m_A;
int m_B;

};
//只能利用全局函数来重载左移运算符
ostream &operator<<(ostream &cout, Person &p)//本质 operator<<(cout,p)简化cout << p
{
cout << "m_A= " << p.m_A << " m_B=" << p.m_B;
return cout;//cout也可以改名字,因为引用的本身是起别名
}
void test01()
{
Person p(10, 10);

cout << p << endl;
}

int main()
{
test01();

system("pause");
return 0;

}
总结:重载左移运算符配合友元可以实现输出自定义数据类型

4.5.3 递增运算符重载

作用:通过重载递增运算符,实现自己的整型数据

//重载递增运算符

//自定义整型
class MyInteger
{
friend ostream &operator<<(ostream&cout, MyInteger myint);
public:
MyInteger()
{
m_Num = 0;
}
//重载前置++运算符  返回引用是为了一直对一个数据进行递增操作
MyInteger& operator++()//如果不返回引用每次递增之后会创建一个新的变量
{
m_Num++;
return *this;
}

//重载后置++运算符
//void operator++(int) int代表占位参数,可以用于区分前置和后置递增 只能用int
MyInteger operator++(int)//后置要返回值,因为返回的是临时变量执行结束之后会释放
{
//先 记录当时结果
MyInteger temp = *this;
//后 递增
m_Num++;
//最后将记录结果做返回
return temp;
}
private:
int m_Num;

};

//重载左移运算符
ostream &operator<<(ostream&cout, MyInteger myint)
{
cout << myint.m_Num;
return cout;
}
void test01()
{
MyInteger myint;
cout << ++myint << endl;
cout << myint << endl;
}
void test02()
{
MyInteger myint;
cout << myint++ << endl;
cout << myint << endl;
}

int main()
{
test01();
test02();

system("pause");
return 0;

}

4.5.4 赋值运算符重载

C++编译器至少给一个类添加四个函数

  1. 默认构造函数(无参,函数体为空)
  2. 默认析构函数(无参,函数体为空)
  3. 默认拷贝构造函数,对属性进行拷贝
  4. 赋值运算符operator=,对属性进行拷贝

如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝问题

//赋值运算符重载

class Person
{
public:
Person(int age)
{
m_Age =new int(age);
}
~Person()
{
if (m_Age != NULL)
{
delete m_Age;
m_Age = NULL;
}
}
//重载赋值运算符
Person& operator=(Person &p)
{
//编译器是提供浅拷贝
//m_Age=p.m_Age;

//应该先判断是否有属性在堆区,如果有先释放干净,然后再深拷贝
if (m_Age != NULL)
{
delete m_Age;
m_Age = NULL;
}
//深拷贝
m_Age = new int(*p.m_Age);

//返回对象本身
return *this;
}
int *m_Age;
};
void test01()
{
Person p1(18);

Person p2(20);

Person p3(30);

p3=p2 = p1;//赋值操作
cout << " p1的年龄为: " << *p1.m_Age << endl;

cout << " p2的年龄为: " << *p2.m_Age << endl;

cout << " p3的年龄为: " << *p3.m_Age << endl;
}
int main()
{
test01();

system("pause");
return 0;

}

4.5.5 关系运算符重载

作用:重载关系运算符,可以让两个自定义类型对象进行对比操作。

//重载关系运算符
class Person
{
public:
Person(string name, int age)
{
m_Name = name;
m_Age = age;
}
//重载==号
bool operator==(Person &p)
{
if (this->m_Name == p.m_Name&&this->m_Age == p.m_Age)
{
return true;
}
return false;
}

bool operator!=(Person &p)
{
if (this->m_Name == p.m_Name&&this->m_Age == p.m_Age)
{
return false;
}
return true;
}

string m_Name;
int m_Age;

};
void test01()
{
Person p1("Tom", 18);

Person p2("Tom", 14);
if (p1 == p2)
{
cout << " p1和p2是相等的! " << endl;
}
else
{
cout << " p1和p2是不相等的! " << endl;
}

if (p1 != p2)
{
cout << " p1和p2是不相等的! " << endl;
}
else
{
cout << " p1和p2是相等的! " << endl;
}
}
int main()
{
test01();

system("pause");
return 0;

}

4.5.6 函数调用运算符重载

  • 函数调用运算符()也可以重载
  • 由于重载后使用的方式非常像函数的调用,因此称为仿函数
  • 仿函数没有固定写法,非常灵活。
//函数调用运算符重载

//打印输出类
class MyPrint
{
public:
//重载函数调用运算符
void operator()(string test)
{
cout << test << endl;
}

};
void MyPrint02(string test)
{
cout << test << endl;
}

void test01()
{
MyPrint myPrint;

myPrint("helloWorld");//由于使用起来非常类似于函数调用,因此称为仿函数

MyPrint02("helloWorld");

}

//仿函数非常灵活,没有固定的写法
//加法类

class MyAdd
{
public:
int operator()(int num1, int num2)
{
return num1 + num2;
}
};

void test02()
{
MyAdd myAdd;
int ret=myAdd(100, 100);
cout << "ret= " << ret << endl;

//匿名函数对象  MyAdd()执行结束后立即被释放 类型+()
cout << MyAdd()(100, 106 )<< endl;

}
int main()
{
test01();
test02();

system("pause");
return 0;

}
匿名函数对象 MyAdd()执行结束后立即被释放 类型+()

 
最后欢迎大家访问我的个人博客青蛙听禅的博客

  • 点赞
  • 收藏
  • 分享
  • 文章举报
青蛙听禅 发布了18 篇原创文章 · 获赞 6 · 访问量 3361 私信 关注
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: