相对于C语言的面向过程来说,C++则是一门面向对象的语言。他的关注点是对象,把一件事情分成不同的对象,利用对象之间的交互来实现用户希望的功能。
类的引入
class className
{
public:
//类中的公有函数和变量
private:
//类中私有的函数和变量
};//和结构体类似,有一个分号
class 是C++中类的关键字,className 是这个类的名字,{ } 中是这个类的主体。类中的元素称为类的成员,其中的数据称为类的属性或者成员变量,类中的函数称为类的方法或者成员函数。
//定义一个简单的日期类
class Data
{
public:
//打印年月日
void Printf()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;//年
int _month;//月
int _day;//日
};
定义类的两种方式:
- 一种是直接把声明和定义放在类中,注意成员函数如果在类中定义,编译器可能会将其当成内联函数处理。
- 另一种是像C语言中分文件操作一样,声明放在.h文件,定义放在.cpp文件中。
XXX.h文件
class Data
{
public:
//打印年月日
void Printf();
private:
int _year;//年
int _month;//月
int _day;//日
};
XXX.cpp文件
#include "XXX.h"
//跟使用命名空间一样
void XXX::Printf()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
直观上看,类和C语言中的struct结构体差不多,C++是向下兼容C语言的,C语言的程序放在C++中也可以正常运行,那么在C++中struct结构体和class的区别就在于,C++中的class的默认成员变量是私有的,但是在struct中,为了向下兼容C语言,使得结构体中的变量默认就是公有的。另外在C++中,strcut结构体中也可以定义类,方式和class中定义类相同。
类定义了一个新的作用域,类中的所有成员都在类的作用域中。如果想要在类外定义成员变量,就需要像命名空间那样,使用::作用域解析符指明成员属于哪一个类域。
类的访问限定符
C++实现封装的方式:用类将对象的属性和方法结合在一起,让对象更加完善,通过访问权限,选择性的将其接口提供给外部的用户使用。
- public:public修饰的成员可以在类外直接被访问。
- protected 和 private 修饰的成员在类外不能被访问,只有在当前类中才可以被访问。
- 访问权限作用于从当前访问限定符的位置开始到下一个访问限定符出现的位置截止。访问限定符也只有在编译的时候会起作用,但是当数据映射到内存中后,就没有访问限定符之间的区别。
- class 中的默认的访问权限是private,struct 中默认的访问权限是public。
封装
在学习高级语言的过程中,听过的最多的话就是,面向对象的三大特征:封装,继承,多态。
- 封装:把数据和操作数据的方法进行有机结合,但隐藏对象的属性和实现的细节,只有对外公开的接口来和对象进行交互。
就像我们在C语言中想要实现一个栈或者队列,本身的代码就不长,只是想用到栈或者队列的一些特性,我们往往就要模拟实现一个栈或者队列,实现之后还要进行测试,判断写的栈或者队列有没有BUG,很浪费时间和性能。但是在C++中有了封装之后,我们只需要在头文件中调用stack 或者 queue 的头文件就可以快速,准确,便捷的实现一个栈或者队列的操作。
封装在本质上就是一种管理,如果在调用栈或者队列中的函数的时候,我们每个人都有权限修改呢?万一改对了那还好,要是改错了就很容易造成其他用户使用时的错误。所以把那些函数封装起来,开放一些共有的成员和成员函数供用户访问,用户具有使用权,但是没有修改权。封装的本质就是一种管理。
类的实例化
每创建出一个类,就相当于建立了一个新的模型。就像我们想创建一个动物类,就可以把动物大概分成天空中的飞禽类,水里的鱼类,还有地面的陆生类等等,然后每个分类中对于每一个动物再细分他们特点,通过成员变量来描述出一个真正存在的事物。
类只是一个模型一样的东西,只要类有想建立的模型,那么这个模型就会限定这个类中会有那些成员,然后定义出一个类并没有分配实际的内存空间来存储他。
一个类中可以实例化出多个对象,每个实例化出的对象都会占用实际的物理空间,来存储成员变量。
class Student
{
public:
void Show();
private:
char _name[10];//姓名
char _sex[10];//性别
int _age;//年龄
};
类的大小
//首先定义一个空类
class A
{
};
//只有成员函数的类
class B
{
public:
void Show();
};
sizeof(A);
sizeof(B);
sizeof(Student);
所以说,一个类的大小,实际上就应该是这个类中所有的"成员变量"之和,存储的时候还是遵循内存对齐的原则。注意空类,不能因为他是一个空类就不给这个空类分配空间,他不占用空间怎么知道这个类是否存在的呢,所以说系统在定义空类的时候,先回给他分配一个地址,让他占个位置,说明下自己的存在感。当他有成员变量的时候,具体大小就会安照成员变量来计算。
this 指针
类中的每一个函数其实都是有一个隐含的参数,即this指针。
void show()
{
cout<<_name<<endl;
}
相当于
void show(Student* this)
{
cout<<this->_name<<endl;
}
C++中通过引入this指针解决该问题,即:C++编译器给每个“非静态的成员函数“增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有成员变量的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。
这里的this指针在python 中就是类的默认参数self,以及在java中就是this成员变量。
this指针的特点:
- 类型:类名* const
- 只能在“成员函数”内部使用
- this指针本质上其实是一个成员函数的形参,是对象调用成员函数时,将对象地址作为实参传递给this形参,所以对象中不存储this指针。
- this指针是成员函数的第一个隐含的指针形参,一般情况下由编译器通过ecx寄存器自动传递,不需要用户传递。
this指针的存储位置
this指针一般存在栈中,有时候还会在寄存器中,this指针是一个隐含的形参,参数的存储位置就是栈中。
通过查看汇编代码,我们可以确认这一猜想,在vs编译器下this指针通过ecx寄存器传递来提高效率。
然后我们再看一下中间调用的子程序。
可以看到,本来应该只有三个参数,但是却有四个传参的过程,在调用子程序的时候,发现出现了四次参数的调用,在查看子程序的时候,发现参数都是push出来的,说明g++中this指针是在栈中存储的。