类和对象-C++对象模型和this指针
15.1 成员变量和成员函数分开存储
在C++中:
- 类内的成员变量和成员函数分开存储。
- 只有非静态成员变量才属于类的对象上。
- 类对象的存储方式为字节对齐,总大小为类中最宽基本类型成员大小×非静态成员变量数量。
示例:
#include<iostream>
using namespace std;
//成员变量和成员函数的分开
class Person
{};
class Person_2
{
int m_A; //非静态成员变量属于类的对象上
static string m_Name; //静态成员变量不属于类的对象上
void func() {} //非静态函数函数不属于类的对象上
static void func2() {} //静态函数函数不属于类的对象上
//只有非静态成员变量属于类的对象上
};
class Person_3
{
int m_A;
double m_B;
char m_C;
};
string m_Name = "zzz";
void test1_01()
{
Person p;
//空对象占用内存空间为:1
//因为编译器会为每个空对象分配一个字节空间,仅仅用来区分不同空对象,使得不同空对象都有一个独一无二的内存位置。
cout << "size of p = " << sizeof(p) << endl;
}
void test1_02()
{
Person_2 p;
//非静态成员变量属于类的对象上,这里有有一个int,所以是4
cout << "size of p = " << sizeof(p) << endl;
}
void test1_03()
{
Person_3 p;
//字节对齐,和最大的对齐,double为8,所以为3*8
cout << "size of p = " << sizeof(p) << endl;
}
int main()
{
test1_01();
test1_02();
test1_03();
system("pause");
return 0;
}
15.2 this指针概念
我们知道在C++中成员变量和成员函数是分开存储的,
每一个非静态成员函数只会诞生一份函数实例, 也就是说多个同类型的对象(一个实例化的多个对象)会共用一块函数代码。
那么问题是:这一块代码是如何区分那个对象调用自己的呢?
C++通过提供特殊的对象指针,this指针,解决上述问题——this指针指向被调用的成员函数所属的对象。
this指针是隐含每一个非静态成员函数内的一种指针,
this指针不需要定义,直接使用即可。
this指针的用途:
- 当形参和成员变星同名时,可用this指针来区分
- 在类的非静态成员函数中返回对象本身,可使用
return *this
(*this就是解引用,将这个指针解引用,就得到对象本身)。注意要指明返回数据类型为这个类的引用的数据类型。
示例:
#include<iostream>
using namespace std;
class Person2
{
public:
Person2(int age)
{
age = age;
}
int age;
};
class Person2_02
{
public:
Person2_02(int age)
{
this->age = age;
}
void PersonAddAge(Person2_02& p)
{
this->age += p.age;
}
Person2_02 PersonAddAge2(Person2_02& p) //因为返回类型是一个对象类型,如果直接用这个类的类型接收,会开辟新的内存来接收返回值
{
this->age += p.age;
cout << typeid(*this).name() << endl; //可以看到(*this)的数据类型是class Person2_02
return *this;
}
Person2_02& PersonAddAge3(Person2_02& p) //因为返回类型是一个对象类型,用引用接收,可以将本体返回。
{
this->age += p.age;
return *this;
}
int age;
};
void test2_01()
{
Person2 p(5);
cout << "p的年龄为:" << p.age << endl;
Person2_02 p1(5);
cout << "p1的年龄为:" << p1.age << endl;
Person2_02 p2(10);
cout << "p2的年龄为:" << p2.age << endl;
p2.PersonAddAge(p1);
cout << "改变后p2的年龄为:" << p2.age << endl;
//如果我想多次调用p2的函数,那么需要每次将对象返回。
//链式编程思想
p2.PersonAddAge2(p1).PersonAddAge2(p1).PersonAddAge2(p1);
//理论上应该是15+5+5+5 = 30,但是结果是15+5=20
//因为每次都是另开辟内存来接收返回的对象,导致不能在原对象上做运算。
cout << "再后来改变后p2的年龄为:" << p2.age << endl;
p2.PersonAddAge3(p1).PersonAddAge3(p1).PersonAddAge3(p1);//20+5+5+5
cout << "最后改变后p2的年龄为:" << p2.age << endl;
}
int main()
{
test2_01();
system("pause");
return 0;
}
15.3 空指针访问成员函数
C++中空指针也是可以调用成员函数的,但是也要注意有没有用到this指针 。
如果用到this指针,需要加以判断保证代码的健壮性。
#include<iostream>
using namespace std;
class Person3
{
public:
void showClassName()
{
cout << "this is Person3 class" << endl;
}
void showPerson3Age()
{
if (this == NULL)
{
return;
}
//报错原因是因为传入的指针为NULL,
cout << "age = " << m_Age << endl; //调用m_Age时,其实是这个代码:this->m_Age,现在this为空,所以报错。
}
int m_Age;
};
void test3_01()
{
Person3* p = NULL;
p->showClassName();
p->showPerson3Age();
}
int main()
{
test3_01();
system("pause");
return 0;
}
15.4 const修饰成员函数
常函数:
- 成员函数后加const后我们称为这个函数为常函数。
- 常函数内不可以修改成员属性。
- 成员属性声明时加关键字
mutable
后,在常函数中依然可以修改。
语法:
//常函数
返回值类型 函数名() const {}
//mutable
mutable 数据类型 变量名;
常对象:
- 常对象的所有成员变量都不可修改。
- 成员属性声明时加关键字
mutable
后,依然可以修改。 - 声明对象前加
const
称该对象为常对象。 - 常对象只能调用常函数。
//常对象
const 对象名 对象;
示例:
#include<iostream>
using namespace std;
class Person4
{
public:
//this指针本质就是一个指针常量(指针的指向不可更改) 类似于Person* const this;
//加上const后,就相当于const Person *const this,即指针指向的值也不可修改。
void showPerson4() const
{
//this = NULL; this指针是不可修改的,
//this->m_A = 100;
this->m_B = 200;
}
void func()
{
m_A = 100;
}
int m_A;
mutable int m_B; //变量加上关键字mutable后,就仍旧是可以更改的。
};
void test4_01()
{
Person4 p;
p.showPerson4();
}
void test4_02()
{
const Person4 p; //变为常对象
//p.m_A = 100;
p.m_B = 200; //m_B加了mutable,在常对象下也是可以修改的。
//常对象只能调用常函数
p.showPerson4();
//p.func();//非常函数不可调用
//因为非常函数内可以修改成员变量,但是常对象已经被规定它的成员变量是不可修改的,所以会报错。
}
int main()
{
test4_01();
system("pause");
return 0;
}