本章通过引入洗衣服以及房子的例子来帮助理解类和对象
本章的大体结构是
面向对象的理解
类
对象
面向对象的理解
--------------这里引入一个洗衣服的例子,对于洗衣服这件事情,面向过程就像是手洗衣服,你需要手动的去拿盆子`放衣服,倒水,倒洗衣粉等等.这些过程你都需要去关心
---------------而面向对象就像是用洗衣机去洗衣服,你只需要把衣服放进洗衣机,倒入洗衣粉,按下电源你就可以不再关注其过程,.
##########用一句话总结上面的事情就是
-------面向过程,你先拿了一个盆子,然后放水,倒入衣服,放洗衣粉......等等,强调的是你通过一个个步骤去完成这件事情,每一个步骤都要你亲力亲为
-------面向对象,你把衣服放进洗衣机里洗. 这里强调的是你和洗衣机这两个对象之间的交互去完成这件事情,你不需要关心别人是怎么做的,你只需要关心你自己就行了.
类
类的全名就是类型,char ,int等内置类型与自定义类统称为类.
与c语言的结构体非常的相似.但对比简单的c语言结构体又多了很多东西
-------类的大小计算
类的大小主要包括类的成员变量和虚函数表(如果有的话),再加上一些对齐规则
对齐规则如下:
1. 第一个成员在与结构体偏移量为0的地址处。
2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
注意:对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。
VS中默认的对齐数为8
3. 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍
-------封装性
类的封装线主要靠访问限定符来保证用法如下
class MyClass
{
//类外可以通过对象访问
public:
int member_1;
void func_1();
//类外不可访问,可被子类继承
protected:
int menber_2();
void func_2();
//类外不可访问,不可被子类继承
private:
int menber_3;
void func_3();
};
//当然,以上限定符是对于类外而言,类内均可随意访问
-------作用域
类的作用域是就像是一块另类的命名空间,可以通过作用域操作符'::'来访问类内的静态成员,以及实现类内已经声明但没有实现的函数
class MyClass
{
public:
void func();
static int menber ;
};
//实现类内声明但没具体实现的函数
void MyClass::func()
{
//具体实现代码
}
//初始化类内静态变量
int MyClass::menber = 3;
//访问类内静态成员
std::cout << MyClass::menber << std::endl;
-------类的默认函数
用类创建对象的过程称为类的实例化,就像一个房子从图纸被建造成一个具体的房子的过程
这个过程主要与下面几个函数有关
----构造函数
class MyClass
{
public:
//构造
MyClass(int menber) :_menber_1(menber), menber_2(1)
{
//也可以在这里干别的事情
}
private:
int _menber_1;
const int menber_2;
};
类实例化的过程会自动执行该函数(不写会默认生成,并对自定义类调用自己的构造函数),方便用来初始化成员变量等,冒号后的称为初始化列表,方便用来初始化const变量 (初始化列表就算没有显式使用,也会生成默认的,然后再运行一遍,所以为了效率,一般能用初始化列表初始化的变量都会用初始化列表初始化)
----拷贝构造
class MyClass
{
public:
//拷贝构造
MyClass(const MyClass& myclass)
{
_menber_1 = myclass._menber_1;
}
private:
int _menber_1;
};
拷贝构造也是一种构造函数,相当于对构造函数进行了重载,当外面传进来的参数是本类实例化的时就会调用该函数实例化对象,(该函数同样可以使用初始化列表, 不写也会默认生成,且为浅拷贝)
----拷贝赋值
class MyClass
{
public:
//拷贝构造
MyClass& operator=(const MyClass& other)
{
//检查是否为自我赋值
if (&other != this)
return *this;
//进行赋值操作
_menber_1 = other._menber_1;
}
private:
int _menber_1;
};
拷贝赋值与拷贝构造的区别就在于用法上
MyClass a(1);
//拷贝构造
MyClass b(a);
//拷贝赋值
MyClass c = a;
----移动构造
移动构造与拷贝构造的区别在于其可以直接掠夺赋值对象的资源,其中的一个应用场景就是在函数返回对象时需要先构造一个临时对象,再用该临时对象进行拷贝构造返回给外部接收,而移动构造则是先构造一个临时对象,在让外部接收的对象直接掠夺临时对象的资源,这对需要深拷贝的对象性能提升极大
class MyClass
{
public:
//移动构造
MyClass& operator=(MyClass&& other)
{
//减少重新开辟空间再赋值的过程,将对象的构造之间转换成指针的交换
_menber_1 = other._menber_1;
other._menber_1 = nullptr;
}
private:
//该指针需要在构造时开辟一块空间给它
int* _menber_1;
};
----移动赋值
移动赋值和拷贝构造与拷贝赋值之间的区别一样,就在于用法不同
-------this指针
this指针是取地址将对象的地址传给成员函数的一个形参,由编译器自己去完成,不需要用户自己传递,也不允许修改指向
下面是关于this指针的两个例题
// 1.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class A
{
public:
void Print()
{
cout << "Print()" << endl;
}
private:
int _a;
};
int main()
{
A* p = nullptr;
p->Print();
return 0;
}
// 2.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class A
{
public:
void PrintA()
{
cout << _a << endl;
}
private:
int _a;
};
int main()
{
A* p = nullptr;
p->PrintA();
return 0;
}
答案是1正常运行,2报错
这是因为成员函数不属于某个对象,而属于类
所以在对成员函数的调用也不依赖某个特定对象,即使是空的对象指针也能调用成员函数(在编译时进行了静态绑定)
相反,函数的调用依赖于特定对象(也即this指针)来提供正确的上下文数据
所以虽然1的this指针为空,但是1的成员函数没有用到对象的成员变量(也即没有对this进行解引用)
所以能正常运行,而2对this进行的解引用所以报错