云计算百科
云计算领域专业知识百科平台

c++类和对象(上)

类的定义

  • class为类的关键字,注意类定义结束时后⾯分号不能省略。
  • 为了区分成员变量,⼀般习惯上成员变量会加⼀个特殊标识,如成员变量前⾯或者后⾯加_或者m 开头,注意C++中这个并不是强制的,只是⼀些惯例,具体看公司的要求。
  • C++中struct也可以定义类,C++兼容C中struct的⽤法,同时struct升级成了类,明显的变化是 struct中可以定义函数,⼀般情况下我们还是推荐⽤class定义类
  • 定义在类⾯的成员函数默认为inline

class Student
{
public:
// 构造函数:使用初始化列表给成员赋值
Student(const string& name, int age, double height, int classId)
: _name(name), _age(age), _height(height), _classId(classId)
{
}

//成员函数
void Hello() const
{
cout << "姓名: " << _name << endl;
cout << "年龄: " << _age << " 岁" << endl;
cout << "身高: " << _height << " 米" << endl;
cout << "班级: " << _classId << " 班" << endl;
}

private:
//成员变量
string _name;
int _age;
double _height;
int _classId;
};

int main()
{
Student stu("张三", 18, 1.75, 2024);
stu.Hello();
return 0;
}

访问限定符

访问限定符

  • class定义成员没有被访问限定符修饰时默认为private,struct默认为public
  • public修饰的成员在类外可以直接被访问;protected和private修饰的成员在类外不能直接被访 问,protected和private是⼀样的
  • 访问权限作⽤域从该访问限定符出现的位置开始直到下⼀个访问限定符出现时为⽌,如果后⾯没有 访问限定符,作⽤域就到}即类结束。

类域

  • 类域影响的是编译的查找规则,下⾯程序中Init如果不指定类域Stack,那么编译器就把Init当成全 局函数,那么编译时,找不到array等成员的声明/定义在哪⾥,就会报错。指定类域Stack,就是知 道Init是成员函数,当前域找不到的array等成员,就会到类域中去查找。

// 声明和定义分离,需要指定类域

void Stack::Init(int n)
{
array = (int*)malloc(sizeof(int) * n);
if (nullptr == array)
{
perror("malloc申请空间失败");
return;
}
capacity = n;
top = 0;
}

实例化

类的定义实现了一个模板,但是要使用就需要用类创建一个对象

#include<iostream>
using namespace std;
class Date
{
public:
void Init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
private:
// 这⾥只是声明,没有开空间

int _year;
int _month;
int _day;
};
int main()
{
}
// Date类实例化出对象d1和d2
Date d1;
Date d2;
d1.Init(2024, 3, 31);
d1.Print();
d2.Init(2024, 7, 5);
d2.Print();
return 0;

类的大小

分析⼀下类对象中哪些成员呢?类实例化出的每个对象,都有独⽴的数据空间,所以对象中肯定包含成员变量,那么成员函数是否包含呢?⾸先函数被编译后是⼀段指令,对象中没办法存储,这些指令存储在⼀个单独的区域(代码段),那么对象中⾮要存储的话,只能是成员函数的指针。再分析⼀下,对象中是否有存储指针的必要呢,Date实例化d1和d2两个对象,d1和d2都有各⾃独⽴的成员变量_year/_month/_day存储各⾃的数据,但是d1和d2的成员函数Init/Print指针却是⼀样的,存储在对象中就浪费了。如果⽤Date实例化100个对象,那么成员函数指针就重复存储100次,太浪费了。这⾥需要再额外哆嗦⼀下,其实函数指针是不需要存储的,函数指针是⼀个地址,调⽤函数被编译成汇编指令[call 地址],其实编译器在编译链接时,就要找到函数的地址,不是在运⾏时找,只有动态多态是在运⾏时找,就需要存储函数地址 内存对齐规则

  • 第⼀个成员在与结构体偏移量为0的地址处
  • 其他成员变量要对⻬到某个数字(对⻬数)的整数倍的地址处
  • 注意:对⻬数=编译器默认的⼀个对⻬数与该成员⼤⼩的较⼩值
  • VS中默认的对⻬数为8
  • 结构体总⼤⼩为:最⼤对⻬数(所有变量类型最⼤者与默认对⻬参数取最⼩)的整数倍
  • 如果嵌套了结构体的情况,嵌套的结构体对⻬到⾃⼰的最⼤对⻬数的整数倍处,结构体的整体⼤⼩ 就是所有最⼤对⻬数(含嵌套结构体的对⻬数)的整数倍

#include<iostream>
using namespace std;
// 计算⼀下A/B/C实例化的对象是多⼤?

class A
{
public:
void Print()
{
cout << _ch << endl;
}
private:
char _ch;
int _i;
};
class B
{
public:
void Print()
{
//…
}
};
class C
{};
int main()
{
A a;
B b;
C c;
cout << sizeof(a) << endl; //8
cout << sizeof(b) << endl; //1
cout << sizeof(c) << endl; //1
return 0;
}

上⾯的程序运⾏后,我们看到没有成员变量的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指针访问的,如Init函数中给_year赋值,this->_year = year;
  • C++规定不能在实参和形参的位置显⽰的写this指针(编译时编译器会处理),但是可以在函数体内显 ⽰使⽤this指针

c++this指针存在在内存的哪里? 关于 this 指针在内存中的位置,核心结论是:它没有固定的存储位置,完全取决于编译器实现和调用约定。

但可以明确的是:this 指针本身绝不存储在对象的内存空间中(对象里只存成员变量)。它本质上是非静态成员函数的一个隐式形参。

根据不同的编译优化级别和硬件平台,它的实际栖身之所主要有以下几种情况:

1. 寄存器(绝大多数高性能场景) 在 x86-64 架构下,编译器会优先使用寄存器传递 this:

  • Windows(x64 调用约定):this 保存在 RCX 寄存器中。
  • Linux / macOS(System V ABI):this 保存在 RDI 寄存器中。
  • ARM 架构:通常保存在 R0(第一个通用寄存器)中。 只要函数体不复杂,编译器会一直通过寄存器访问 this,此时它不存在于内存中,只在 CPU 内部流转。

2. 栈内存(寄存器溢出或被取地址时) 当函数逻辑复杂,寄存器不够用时,编译器会把 this 的值从寄存器“溢出”到**当前函数的栈帧(Stack Frame)**中,保存为一个局部临时变量。此外,在 x86(32位)传统的 thiscall 调用约定下,this 会被直接压入调用栈中,作为函数参数传递。

3. 全局/静态存储区(极特殊情况) 如果对象是全局对象或静态局部对象,this 指针的值(即地址)指向全局/静态存储区。但请注意,这指的是 this 指向的内容在那里,而 this 指针变量本身(若被存到内存里)依然在函数的栈帧或寄存器中。

4. Lambda 捕获器内部 如果 Lambda 表达式捕获了 this([this] 或 [=]),那么这个 this 指针的值会被存储在 Lambda 闭包对象(Closure) 的内存中。如果这个 Lambda 是局部变量,闭包就在栈上;如果被 new 出来,就在堆上。


补充两个容易混淆的冷知识:

  • this 是右值:C++ 标准规定 this 是一个纯右值(prvalue),你不能对 this 取地址(&this 是编译错误),所以不存在“this 指针的地址”这一说法,只有“this 指向的对象的地址”。
  • 虚函数表(vptr):虽然 this 不在对象里,但对象内存布局的最前面通常有一个指向虚函数表(vtable)的指针(vptr)。当调用虚函数时,编译器会通过 this 找到这个 vptr,再找到真正的函数地址。

总结一句话:平时运行中它优先待在寄存器里,需要保存时才临时放在栈上,但永远不属于对象本身。 开发时无需关心它的物理位置,交给编译器优化即可。

赞(0)
未经允许不得转载:网硕互联帮助中心 » c++类和对象(上)
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!