什么是类?
在C++中,结构体内不仅可以定义变量,也可以定义函数。而类Class就可以看出是c++中的改进结构体,有很多新特性。
类的定义:
class ClassName
{
// 类体:由成员函数和成员变量组成
}; // 不要忘记有分号
- 类中的数据称为类的属性或者成员变量;
- 类中的函数称为类的方法或者成员函数。
推荐定义类的方法是:将类的声明放在.h头文件中,将类的定义放在.c文件中。
例如:
.h
class Date
{
public:
void SetDate(int year, int month, int date);
private:
int _year;
int _month;
int _date;
};
.c
#include "data.h"
void Date::SetDate()
{
_year = year;
_month = month;
_date = month;
}
类的访问限定符
public:在类域外面也可以通过类直接访问
private:在类域外面不能访问
protected:在类域外面不能访问,在派生类中可访问,用于继承关系中,提供一种受限的访问方式,使得子类可以访问父类的某些成员,同时保持对外界的隐藏。
注意:访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别
类的作用域和实例化
-
类定义了一个新的作用域,类的所有成员都在类的作用域中**。**在类体外定义成员,需要使用::作用域解析符指明成员属于哪个类域。
class Date { public: void SetDate(int year, int month, int date); void PrintDate(); private: int _year; int _month; int _date; }; void Date::PrintDate() { cout<<_year<<"-"<<_month<<"-"<<_date<<endl; }
-
用类类型创建对象的过程,就是类的实例化
ps:可以把类理解为一个图纸,而对象就是根据这个图纸建造的房子或者设备。
类对象模型(大小、存储方式、内存对齐规则)
1.类对象的存储方式
先说结论:一个类的大小,实际就是该类中”成员变量”之和,当然也要进行内存对齐;
注意空类的大小,空类比较特殊,编译器给了空类一个字节来唯一标识这个类。
2.类与结构体内存对齐规则
类和对象遵守结构体的内存对齐规则:
- 第一个成员在与结构体偏移量为0的地址
- 其他成员变量要对齐到对齐数的整数倍的地址处
- 对齐数 = 编译器默认对齐数 与 该成员大小 的较小值
- 结构体总大小 = 最大对齐数的整数倍
- 最大对齐数是 所有变量类型大小的最大值 与 默认对齐参数 取较小值
- 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
一些面试题
- 结构体怎么对齐? 为什么要进行内存对齐?
上面就描述了对齐规则,分为三个方面:起始成员变量偏移量为0、其余成员变量对齐、总大小要对齐。
内存的对齐的原因:
-
平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数
据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。(早期的 ARM 处理器(如 ARM7、ARM9)要求内存访问必须对齐)
-
性能原因:应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
- 如何让结构体按照指定的对齐参数进行对齐?
-
#pragma pack
指令用来设置结构体的对齐方式。你可以指定一个数字来表示最大对齐值。-
把原来对齐方式设置压栈,并设新的对齐方式设置为1个字节对齐
#pragma pack(push, 1)
-
恢复对齐状态
#pragma pack(pop)
-
eg:
#include <stdio.h> #include <stddef.h> #pragma pack(push, 1) // 设置最大对齐为 1 字节 struct MyStruct { char c; int i; double d; }; #pragma pack(pop) // 恢复之前的对齐设置 int main() { printf("sizeof(MyStruct) = %zu\n", sizeof(struct MyStruct)); return 0; }
-
-
使用
__attribute__((packed))
来确保结构体的成员紧密排列,不考虑对齐要求。-
eg:
#include <stdio.h> #include <stddef.h> struct MyStruct { char c; int i; double d; } __attribute__((packed)); int main() { printf("sizeof(MyStruct) = %zu\n", sizeof(struct MyStruct)); // sizeof(MyStruct) = 13 return 0; }
-
- 什么是大小端?如何测试某台机器是大端还是小端,有没有遇到过要考虑大小端的场景
-
大端:高字节序存储在起始/低地址空间
-
小端:低字节序存储在起始/低地址空间
-
使用联合体可以进行测试
typedef union { unsigned int i; unsigned char ch[4]; }Endian; int main() { Endian testEndian; testEndian.i = 0x12345678; if(testEndian.ch[0] == 0x12) { printf("Big-endian\n"); } else if(testEndian.ch[0] == 0x78) { printf("Little-endian\n"); } else { printf("Unknown-endian\n"); } }
this指针
- this指针是一个成员方法的隐藏形参,是对象调用成员函数时,将对象地址作为实参传递给this形参。所以对象中不存储this指针。
- 一般情况由编译器通过ecx寄存器自动传递,不需要用户传递
eg:前面的日期类
void Date::PrintDate() -> void Date::PrintDate(Data* const this)
{
cout<<_year<<"-"<<_month<<"-"<<_date<<endl;
}
指针
- this指针是一个成员方法的隐藏形参,是对象调用成员函数时,将对象地址作为实参传递给this形参。所以对象中不存储this指针。
- 一般情况由编译器通过ecx寄存器自动传递,不需要用户传递
eg:前面的日期类
void Date::PrintDate() -> void Date::PrintDate(Data* const this)
{
cout<<_year<<"-"<<_month<<"-"<<_date<<endl;
}