C++从0到1 - 黑马程序员 课程学习笔记
课程链接:32 类和对象-对象特性-成员变量和成员函数分开存储_哔哩哔哩_bilibili
3. C++对象模型和this指针
3.1 成员变量和成员函数分开存储
在C++中,类内的成员变量和成员函数分开存储
只有非静态成员变量才属于类.
空对象占用内存空间为:1 个字节
原因:C++编译器会给每个空对象分配一个字节空间,是为了区分空对象占内存的位置
每个空对象也应该有一个独一无二的内存地址
3.2 this指针概念
每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象回共用一块代码,那么,这一块代码如何区分哪个对象调用自己呢?
C++通过提供特殊的对象指针——this指针,区分哪个对象调用成员函数代码,this指针指向被调用的成员函数所属的对象
this指针是隐含在每一个非静态成员函数的一种指针
this指针不需要定义,直接使用即可
this 指针用途:
- 当形参和成员变量同名时,可用this指针来区分
- 在类的非静态成员函数中返回对象本身,可以return *this
class Person
{
public:
Person(int age)
{
// age = age; // 形参名称与成员变量名称一致,无法区分
this -> age = age; // this指针指向被调用的成员函数所属的对象
}
Person& PersonAdd(Person &p) // 返回本体应该使用引用
// 如果返回的是值,则不是P2本体,而是调用拷贝构造函数重新构造了一个新的数据
{
this -> age += p.age;
return *this;
}
int age;
};
// 解决名称冲突
void test01()
{
Person p1(18);
}
// 返回对象本身return *this
void test02()
{
Person p1(18);
Person p2(18);
// 链式编程思想
p2.PersonAdd(p1).PersonAdd(p1); // 返回值为空时,报错
}
3.3 空指针访问成员函数
C++中空指针也可以调用成员函数,但是要注意有没有用到this指针,若用到this指针,则需要加以判断保证代码的健壮性.
class Person
{
public:
void showClassName()
{
cout << "Person Class" << endl;
}
void showPersonAge()
{
if(this == NULL) // 防止空指针程序崩溃
{
return;
}
cout << "Age" << m_Age << endl; // 默认为this->m_Age
}
int m_Age;
};
void test01()
{
Person * p = NULL;
p->showClassName(); // 不报错
// p->showPersonAge(); // 报错 原因为传入指针为NULL
}
3.4 const修饰成员函数
常函数:
- 成员函数后 加const修饰 -> 常函数
- 常函数内不可以修改成员属性
- 成员属性声明时加关键字mutable后,在常函数中依然可以修改
常对象:
- 声明对象前 加const -> 常对象
- 常对象只能调用常函数
class Person
{
public:
// this指针的本质是 指针常量,指针的指向是不可以修改的
// 再次添加const之后,指针指向的值也不可以修改
// 在成员函数后加const,修饰的是this指针,让指针指向的值也不可以修改
void showPerson() const
{
// ( this -> ) m_A = 100;
m_B = 100;
}
void func(){}
int m_A;
mutable int m_B; // 特殊变量 在常函数也可以修改
};
void test()
{
const Person p; // 对象前加const 变为常对象
// p.m_A = 100; // 常对象不可以修改
p.m_B = 100;
// p.func(); // 常对象只能调用常函数, 普通成员函数可以修改属性,常对象不可以
}
4. 友元
在程序中,有些私有属性想要类外特殊的一些函数或者类进行访问,就要用到友元技术.
友元的目的就是让一个函数或者类访问另一个类中私有成员
友元关键字是 friend
友元的三种实现:
- 全局函数做友元
- 类做友元
- 成员函数做友元
4.1 全局函数做友元
在类内起始位置以friend前置声明 friend void goodGay(Building *building);
class Building
{
// 表明友元函数
friend void goodGay(Building *building);
public:
Building()
{
m_SittingRoom = "客厅";
m_BedRoom = "卧室";
}
string m_SittingRoom;
private:
string m_BedRoom;
};
// 全局函数
void goodGay(Building *building)
{
cout << "goodGay" << building->m_SittingRoom <<endl;
cout << "goodGay" << building->m_BedRoom <<endl;
}
void test01()
{
Building building;
goodGay();
}
4.2 类做友元
在类内起始位置以friend前置声明类名 friend class GoodGay;
class Building;
class GoodGay
{
public:
GoodGay();
void visit(); // 参观函数 访问building中属性
Building *building;
};
// 类外写函数
GoodGay::GoodGay()
{
building = new building;
}
void GoodGay::visit()
{
cout << "goodGay" << building->m_SittingRoom <<endl;
cout << "goodGay" << building->m_BedRoom <<endl;
}ds
class Building
{
friend class GoodGay;
public:
Building()
{
m_SittingRoom = "客厅";
m_BedRoom = "卧室";
}
public:
string m_SittingRoom;
private:
string m_BedRoom;
};
4.3 成员函数做友元
在类内起始位置以friend前置声明成员函数 friend void GoodGay::visit();
5. 运算符重载
运算符重载概念:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型
5.1 加号运算符重载 +
作用:实现两个自定义数据类型相加的运算
- 全局重载的优先级高于成员函数重载
- 运算符重载也可以发生函数重载
// 成员函数重载
class Person
{
public:
Person operator+(Person &p)
{
Person temp;
temp.m_A = this->m_A + p.m_A;
return temp;
}
int m_A;
};
// 全局函数重载
Person operator+(Person &p2,Person &p2)
{
Person temp;
temp.m_A = p1.m_A + p2.m_A;
return temp;
}
5.2 左移运算符重载
作用:输出自定义类型
// 利用成员函数重载左移运算符 p.operator<<(cout)简化版本p<<cout
// 不会利用成员函数重载左移运算符 因为无法实现cout在<<左侧
void operator<<(ostream cout)
// 全局函数重载左移运算符
ostream operator<<(ostream &cout,Person &p) // 本质operator<< (cout,p) 简化 cout<<p
{
return cout;
}
5.3 递增运算符重载 ++
作用:实现自己的整型数据
// 内置递增运算符
int a = 10;
cout << ++a << endl;//11
cout << a << endl; //11
int b = 10;
cout << b++ << endl;//10
cout << b << endl; //11
// 自定义整形
class MyInteger
{
friend ostream operator<<(ostream &cout, MyInteger &myint) ;
public:
MyInteger()
{
m_Num = 0;
}
// 重载前置递增 返回为引用,为了一直对一个数据进行递增
MyInteger& ostream operator++()
{
m_Num++;
return *this;
}
// 重载后置递增 int代表展位参数,用于区分前置和后置
// 后置返回值
MyInteger ostream operator++(int)
{
// 先记录当时结果
MyInteger temp = *this
// 后递增
m_Num++;
// 返回当时结果
return temp;
}
private:
int m_Num;
};
// 重载左移运算符
ostream operator<<(ostream &cout, MyInteger &myint) // 本质operator<< (cout,p) 简化 cout<<p
{
cout << muint.m_Num;
return cout;
}
5.4 赋值运算符重载 =
C++编译器至少给一个类添加四个函数:
- 默认构造函数
- 默认析构函数
- 默认拷贝构造函数
- 赋值运算符 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)
{
// 先判断是否有属性在堆区,如果有,先释放干净,再进行深拷贝
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);
p2 = p1; // 赋值操作
cout << "p1 age " << *p1.m_Age << endl;
cout << "p2 age " << *p2.m_Age << endl;
}
5.5 关系运算符重载 > < == !=
作用:可以让两个自定义类型对象进行比较
bool operator==(Person &p)
{
...
return true/false;
}
5.6 函数调用运算符重载 ()
由于重载后使用的方式非常像函数的调用,因此称为仿函数
仿函数没有固定写法,非常灵活
class MyPrint()
{
void operator()(string test)
{
cout << test << endl;
}
}
int main()
{
MyPrint myPrint;
myPrint("hello world"); // 十分像函数调用 因此称为仿函数
// 匿名函数对象
cout << MyPrint()("hello") << endl;
}