一、类的定义
1、类定义格式
- class为定义类的关键字,Stack为类的名字,{}中为类的主体,注意类定义结束时后面分号不能省略。类体中内容称为类的成员:类中的变量称为类的属性或成员变量;
类中的函数称为类的方法或者成员函数。- 为了区分成员变量,⼀般习惯上成员变量会加⼀个特殊标识,如成员变量前面或者后面加_ 或者 m开头,小编一般会在成员变量前面加_。
- C++中struct也可以定义类,C++兼容C中struct的⽤法,同时struct升级成了类,明显的变化是struct中可以定义函数,⼀般情况下我们还是推荐⽤class定义类。
- 定义在类⾯的成员函数默认为inline。
(当我们用多个文件管理项目工程时,比如后面我们要实现的日期类,一般类都定义在头文件中,当我们某个文件要访问这个类时就需要包含定义了这个类的头文件,如果类里的成员函数直接定义在类里,成员函数默认是内联的,当有多个文件都包含了这个头文件时就不会发生链接错误,因为内联函数直接展开,不会进符号表。当类里的成员函数声明和定义分离时,也不会报错,因为普通函数本来就必须声明和定义分离,这样一想其实逻辑还蛮自洽的。)
2、访问限定符
- C++⼀种实现封装的方式,⽤类将对象的属性与方法结合在⼀块,让对象更加完善,通过访问权限选择性的将其接口提供给外部的用户使用。
- public修饰的成员在类外可以直接被访问;protected和private修饰的成员在类外不能直接被访问,protected和private是⼀样的,以后介绍到继承章节才能体现出它们的区别。
- 访问权限作用域从该访问限定符出现的位置开始直到下⼀个访问限定符出现时为止,如果后面没有访问限定符,作⽤域就到 }即类结束。
- class定义成员没有被访问限定符修饰时默认为private,struct默认为public。
- ⼀般成员变量都会被限制为private/protected,需要给别人使用的成员函数会放为public。
class Date
{
public:
//成员函数(函数声明或者定义都可以放)
private:
//成员变量
int _year;
int _month;
int _day;
};
3、类域
1、类定义了⼀个新的作用域,类的所有成员都在类的作用域中,在类体外定义成员时,需要使用 :: 作用域操作符指明成员属于哪个类域。
2、类域影响的是编译的查找规则,上面程序中Date函数是下一节要介绍的构造函数,Date如果不指定类域Date,那么编译器就把Date当成全局函数,那么编译时,找不到_year等成员变量的声明/定义在哪⾥,就会报错。指定类域Date,就是知道Date是成员函数,当前域找不到的_year等成员,就会到类域中去查找。
3、我们这里认识类域还要把它和命名空间域进行比较,类域主要解决的是类和类之间的命名冲突,比如stack和list都可以定义push函数,命名空间域解决的是全局的函数/变量/类型的命名冲突问题,比如在两个命名空间可以分别定义一个stack类。
4、还要区分类域和局部域,我们知道局部域和全局域会影响变量的生命周期,而类域和命名空间域不会影响生命周期,所以类域和局部域是两个平行的东西,不要混一起。
二、实例化
1、实例化概念
- 用类类型在物理内存中创建对象的过程,称为类实例化出对象。
- 类是对象进行⼀种抽象描述,是⼀个模型⼀样的东西,限定了类有哪些成员变量,这些成员变量只是声明,没有分配空间,⽤类实例化出对象时,才会分配空间。
- ⼀个类可以实例化出多个对象,实例化出的对象 占⽤实际的物理空间,存储类成员变量。打个比方:类实例化出对象就像现实中使⽤建筑设计图建造出房⼦,类就像是设计图,设计图规划了有多少个房间,房间⼤⼩功能等,但是并没有实体的建筑存在,也不能住⼈,用设计图修建出房⼦,房⼦才能住⼈。同样类就像设计图⼀样,不能存储数据,实例化出的对象分配物理内存存储数据。
class Date
{
public:
void Init(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
void print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
private:
//只是声明
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
d1.Init(2025, 5, 12);
d1.print();
return 0;
}
2、对象大小
- 我们要先清楚一个点,对象并不会存成员函数的地址(函数地址就是函数编译后生成指令的第一句指令的地址),因为调用函数本质就是call函数的地址,函数创建出来后它的地址被统一存在内存的常量区,同一类不同的对象调用其中同一个成员函数本质都是call这个函数的地址(当前文件有函数的定义就在编译时确定函数的地址,当前文件没有定义要到其他文件找就在编译时确定函数地址),所以成员函数的指针并不需要用每个对象独立的空间来存储。
但是成员变量不一样,大多数情况下不同对象的成员变量值都是不一样的,所以每个对象的成员变量需要各自独立的空间来存储各自独立的值就很有必要。
- 我们再复习一下C语言阶段学过的结构体内存对齐规则,计算对象的大小同样适用:
1、第⼀个成员在与结构体偏移量为0的地址处。
2、其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
注意:对齐数 = 编译器默认的⼀个对齐数 与 该成员大小的较小值。
VS中默认的对齐数为8
3、 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。
这里小编还想介绍一下面试中经常被问到的问题,为什么要内存对齐呢?
其实是为了提高读取效率,用空间换时间,因为底层硬件设计的原因,读数据一般是从整数倍位置开始读,如果不内存对齐,很有可能出现本来读一次就行了可是因为没有内存对齐要读多次的情况。
- 上面的程序运行后,我们看到没有成员变量的B和C类对象的大小是1,为什么没有成员变量还要给1个字节呢?因为如果⼀个字节都不给,怎么表示对象存在过呢!所以这⾥给1字节,纯粹是为了占位标识对象存在。
三、this指针
- Date类中有 Init 与 Print 两个成员函数,函数体中没有关于不同对象的区分,那当d1调用Init和Print函数时,该函数是如何知道应该访问的是d1对象还是d2对象呢?那么这里就要看到C++给了⼀个隐含的this指针解决这里的问题。
- 编译器编译后,类的成员函数默认都会在形参第⼀个位置,增加⼀个当前类类型的指针,叫做this指针。比如Date类的Init的真实原型为:
void Init(Date* const this, int year,int month, int day)。- 类的成员函数中访问成员变量,本质都是通过this指针访问的,如Print函数中打印_year:this->_year = year。
- C++规定不能在实参和形参的位置显示的写this指针(编译时编译器会处理),但是可以在函数体内显示使用this指针。
- 还有一点,其实this指针本身是被const修饰的:Date* const this,也就是说this指针本身无法被修改,但是它指向的内容可以被修改。
- 还有最后一点,this指针是存在内存的哪个区域呢,有的读者可能会认为是存在对象里面的,实则不然,因为我们之前计算对象大小时并没有算this指针。其实this指针就是成员函数的形参,实参传递给形参就是函数创建栈帧时将实参压栈压入栈顶,所以this指针是存在栈空间里的,但是因为this在函数里有可能会被频繁调用,为了效率有些编译器会把this指针存在寄存器里。
以上就是小编分享的全部内容了,如果觉得不错还请留下免费的赞和收藏
如果有建议欢迎通过评论区或私信留言,感谢您的大力支持。
一键三连好运连连哦~~