文章目录
类的定义及访问限定符
类的格式:
- class为类的关键字,class + 类的名字(自定义)+ {} + ;
- 类中可以定义成员变量也可以定义成员函数,在类中定义的成员函数默认是内联函数,变量为类的属性,函数为类的方法
- 类的成员在定义时可加上一些特殊的标识符,便于区分,也可不加
例:
cpp
#include <iostream>
using std::cout;
using std::endl;
//用类来定义一个栈
class stack // stack为类的名字
{
//类的成员函数
void Init(int capacity = 10)
{
_arr = (int*)malloc(sizeof(int) * capacity);
if(_arr == nullptr)
{
perror("malloc fail!");
exit(-1);
}
_capacity = capacity; //有了特殊标识符就可区分,哪个是成员变量,哪个是形参了
_top = 0;
}
//类的成员变量
int* _arr; // "_"为特殊标识符
int _capacity;
int _top;
};
int main()
{
//...
return 0;
}
类与结构体:
从上面的代码可以看到,类的格式与C语言中的结构体相似,但是C语言中的结构体成员只允许有变量,而不能有函数,所以C++对C的结构体进行了"升级",在C++中,也可以用结构体来定义类
通过上面的代码,C++也可以用结构体定义类,但更多的时候,大家还是选择用class来定义
类的访问与访问限定符的关系:
- 对类进行访问时,首先要定义类的对象,对象其实就是C语言中的变量
- 定义的对象的类型就是自定义的类名
- 访问类的成员与访问结构体的成员是一样的,用到" . "操作符或者"->"操作符
cpp
class A
{
void Print()
{
cout << "hello world!" << endl;
}
int _a;
};
int main()
{
A a; // a为类定义出来的对象,类名就是该对象的类型,类和对象也由此体现了出来
a.Print(); // 通过"."操作符来访问类的成员函数
return 0;
}
接着来看,若将该代码放到本地来运行,结果如下:
出现这样的错误与访问限定符有关,介绍一下访问限定符:
- public
- private
- protected
这三个就是访问限定符,作用在类中,public表示可直接对类进行访问,private 和 protected 表示不可直接对类进行访问;限定符的作用域是从该访问限定符开始到下一个访问限定符出现为止,或者没有访问限定符的出现,直接到类的作用域结束;class在没有定义访问限定符时,默认是private,而struct默认是public,所以这也是为什么上面的代码会出现访问错误的原因,上面的成员函数和成员变量被作用在private中,不可直接对其进行访问;一般的,成员变量被设在private 和protected 中,而需要给别人使用的成员函数就被设为protected
类域
类域定义了一个新的作用域,类的成员变量和成员函数都放在作用域中,与命名空间的访问类似,在类体外访问域里的成员时,需要用到": :"域作用限定符,指明要访问的成员属于哪个类域,类域影响的是编译的查找规则
如上图所示,在不同的两个及以上的文件中,定义类的成员函数时,就需用到域作用限定符,防止在同一个文件下出现两个同名函数时,定义的时候产生矛盾。如,在stack.h中再定义一个队列的类,类中的初始化成员函数名也是Init时,在.cpp文件中定义时,没有明确是哪个类域的初始化,就会产生矛盾,定义成员变量时,亦如此
类的实例化
概念:用类的类型创建对象的过程,就叫作类的实例化
cpp
include <iostream>
using std::cout;
using std::endl;
class A
{
public:
void Print(int x)
{
_a = x;
cout << _a << endl;
}
private:
int _a;
};
int main()
{
// 这就是实例化出对象
// 用类的类型A创建出来的对象a1,a2,a3
A a1;
A a2;
A a3;
return 0;
}
类相当于一个模型一样的东西,限定了类中有哪些成员变量,这写成员变量只是声明,并没有分配空间,只有用类实例化出对象时,才会分配空间(注:没有分配空间的是声明,分配了空间的才是定义),打个比方,创建出来的类相当于一张建房子要用到的图纸,图纸上有房子的整体外观,房子面积多大,高是多少,各种各样的数据,然后通过图纸来建房,建出来的房子就是通过图纸实例化出来的。类的成员变量没有空间,它就相当于图纸,图纸里的房子只是模板,不能住人,而只有实例化出来的对象才需要分配空间,通过图纸建出来的房子才可以住人,才需要空间。
对象的大小
通过上面的打印结果可知:
- 对象的大小只考虑成员变量,不考虑成员函数
- 对象的大小遵循内存对齐的规则
为什么不考虑成员函数?
对象的大小只考虑成员变量,是因为每个对象所要存储的数据不同,所以要为每个变量开辟空间,来存储数据,而函数在被定义编译时,就已经确定了,编译完之后会生成一条条指令,第一条指令就是函数的地址,而每个对象在调用函数时,都会通过函数的地址进行调用,所以每个对象所调用的函数都是同一个函数,若每个对象中都存储一个相同的函数,就显得多余了,所以系统会将函数放到一个公共的区域,每个对象需要调用时,就可直接调用
从图中可以看到,d1和d2同时调用Init函数,call指令就是进入函数的指令,且两个对象的call指令的地址都相同
为什么要内存对齐?
所以由上图可知,内存对齐是为了提高读取数据的速度
this指针
从上面的图片中看到,不同的对象调用的函数是相同的,那为什么打印出来的数据却不同呢?出现这样的结果是因为this指针在里面起到了作用
- 编译器编译后,默认会在类的成员函数的形参的第一个位置增加一个当前类型的this指针
cpp
class date
{
public:
void Init(int year, int month, int day)
// 本质:void Init(date* const this,int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Print()
// 本质:void Print(date* const this)
{
cout << _year << "/" << _month << "/" << _day << endl;
// 本质:cout << this->_year << "/" << this->_month << "/" << this->_day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
date d1;
date d2;
d1.Init(2024, 12, 19); //本质:d1.Init(&d1,2024,12,19);
d2.Init(2024, 8, 8);//本质:d2.Init(&d2,2024,8,8);
d1.Print(); // 本质:d1.Print(&d1);
d2.Print();// 本质:d2.Print(&d1);
return 0;
}
正因为有了this指针,所以才能做到打印出来的数据不同
- 在类的成员函数中访问成员变量时,本质上都是通过this指针来进行访问的
从上面的代码就了知道了,但大多数情况下,this指针是不会被写出来的
- C++规定不能在实参和形参的位置显示地写this指针,但在成员函数体中可显示
可看到显示this指针之后出现了错误,但在函数体中是可以显示的