找大学生做家教去哪个网站找好,久久建筑网平台,市场seo是什么意思,微信公众号预约前言#xff1a; 本章知识点#xff1a; 类对象模型、 this 指针 专栏#xff1a; C初阶 目录 类对象模型 如何计算类对象的大小 类对象的存储方式猜测 结构体内存对齐规则 this指针 this指针的引出 this指针的特性 C语言和C实现Stack的对比 C语言实现 C实现 类对象模型
…前言 本章知识点 类对象模型、 this 指针 专栏 C初阶 目录 类对象模型 如何计算类对象的大小 类对象的存储方式猜测 结构体内存对齐规则 this指针 this指针的引出 this指针的特性 C语言和C实现Stack的对比 C语言实现 C实现 类对象模型
如何计算类对象的大小 代码演示 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;cout sizeof(Date)endl;//?cout sizeof(d1) endl;//?
} Date类实例化了一个d1对象分别求出它们占用的字节大小是多少 为什么结果都是12 解析对于Date来说它也是一个数据类型那么算类型大小就是算这个类型的对象实例化出来有多大。 所以说,sizeof(类名)就相当于算该类的实例化对象有多大即算类的大小就是算对象的大小。 问题类中既可以有成员变量又可以有成员函数那么一个类的对象中包含了什么如何计算一个类的大小看下文。 类对象的存储方式猜测 设计方式一对象中包含类的各个成员 缺陷 每个对象中成员变量是不同的但是调用同一份函数如果按照此种方式存储当一 个类创建多个对象时 每个对象中都会保存一份代码相同代码保存多次浪费空间。 那么如何解决呢 设计方式二代码只保存一份在对象中保存存放代码的地址 设计方式三只保存成员变量成员函数存放在公共的代码段 问题对于上述三种存储方式那计算机到底是按照那种方式来存储的 采用方式三两种证明思路 思路一对于猜测一再重新看上面的代码右击打开反汇编 由d1和d2两个不同的对象分别调用Init()和Print()可以右边汇编代码看到 红色(Init)和 蓝色(Print)框里面的值(地址值)分别对应相等说明什么说明不同对象在调用同一个类里的同一个成员函数它们调用的都是同一个函数这个函数位于 公共的代码区域。 思路二 对于猜测二还有另一种为什么对象采用第三种设计方式我的理解是 但是结果是12所以成员函数并不在对象中存储而是存储在 公共代码区。 我们再通过对下面的不同对象分别获取大小来分析看下 //类中有成员变量和成员函数
class A1
{
public:void f1() {}
private:char _ch;int _a;
};
//类中仅有成员函数
class A2
{
public:void f2() {};
};
//类中什么都没有 -- 空类
class A3
{
};int main()
{
//变量定义的特点就是开空间A1 a1;A2 a2;A3 a3;cout sizeof(a1) endl;//8//分配1byte不存储数据只是占位表示对象存在过cout sizeof(a2) endl;//1cout sizeof(a3) endl;//1
} 通过sizeof操作符算出三个对象的大小a1大小为8 bytea2为1 bytea3为1 byte。 结论一个类的大小实际就是该类中”成员变量”之和当然要注意内存对齐注意空类的大小空类比较特殊编译器给了空类一个字节来唯一标识这个类的对象 结构体内存对齐规则 1. 第一个成员在与结构体偏移量为0的地址处。 2. 其他成员变量要对齐到某个数字对齐数的整数倍的地址处。 3. 结构体总大小为最大对齐数所有变量类型最大者与默认对齐参数取最小的整数倍。 4. 如果嵌套了结构体的情况嵌套的结构体对齐到自己的最大对齐数的整数倍处结构体的整 体大小就是所有最大对齐数含嵌套结构体的对齐数的整数倍。 对齐数 编译器默认的一个对齐数 与 该成员大小的较小值。VS中默认的对齐数为8) C语言内存对齐详解结构体内存对齐 【面试题】 1. 结构体怎么对齐 为什么要进行内存对齐 2. 如何让结构体按照指定的对齐参数进行对齐能否按照3、4、5即任意字节对齐 3. 什么是大小端如何测试某台机器是大端还是小端有没有遇到过要考虑大小端的场景 this指针 this指针的引出 我们先来定义一个日期类 Date 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;//实例化d1对象d1.Init(2023,8,24);//设置d1的日期d1.Print();//打印d1的日期Date d2;//实例化d2对象d2.Init(2024,7,4);//设置d2的日期d2.Print();//打印d2的日期return 0;
} 对于上述类有这样的一个问题:Date类中有 Init 与 Print 两个成员函数函数体中没有关于不同对象的区分那当d1调用 Init 函数时该函数是如何知道应该设置d1对象而不是设置d2对象呢 C中通过引入this指针解决该问题即 C编译器给每个“非静态的成员函数“增加了一个隐藏的指针参数让该指针指向当前对象(函数运行时调用该函数的对象)在函数体中所有“成员变量” 的操作都是通过该指针去访问。只不过所有的操作对用户是透明的即用户不需要来传递编译器自动完成。 也就是说编译器会在编译时进行处理在传参的时候在原本函数的第一个参数的前面添加一个地址作为新的参数 需注意的是this在实参和形参位置不能显示写 但是在类里面是可以显示着用这个this指针可以写可以不写如果不写编译器会自动帮忙完成。 这样看来this指针实际上是用来接收类实例化之后的对象的地址可以理解为this指向了当前调用的对象。
不同的域里面的this值是否相等取决于是否是由同一个对象调用的。不同域内的this不是同一个变量是形参地址值可以一样因为此时指向了当前调用的对象。 this指针的特性
1. this指针的类型类类型* const即成员函数中不能给this指针赋值。 2. 只能在“成员函数”的内部使用 3. this指针本质上是“成员函数”的形参当对象调用成员函数时将对象地址作为实参传递给 this形参。所以对象中不存储this指针。 4. this指针是“成员函数”第一个隐含的指针形参一般情况由编译器通过ecx寄存器自动传 递不需要用户传递 【面试题】 咱们之前都是先实例化对象然后取对象的地址传给this指针那我现在直接定义一个指针呢 1.下面程序编译运行结果是A、编译报错 B、运行崩溃 C、正常运行 class A
{
public:void Print(){cout Print() endl;}
private:int _a;
};
int main()
{A* p nullptr;//对象的地址为nullptrp-Print();return 0;
} 答案:C 解析首先要搞清楚一件事情对空指针解引用不会报编译错误。那这里的意思p-Print()是对空指针p解引用的意思吗它不会运行崩溃吗看下文。 我们知道在C中如果 p 是一个指向结构体或类类型的指针比如 struct S *p;其中 S 有一个成员变量 a那么 p-a 实际上是等价于 (*p).a。这里的 *p 先对指针 p 进行解引用得到结构体或类对象然后通过.运算符来访问该对象的成员变量 a 但是以下情况不同这个Print()函数位于公共代码区域p-print()仅仅只是在传递地址值给this指针接收中间没有解引用操作。 那么怎么才是解引用呢 140行才属于解引用操作。首先138行的Print()成员函数不在对象里面类对象里面只有成员变量。 我们看以上代码可以知道p指针指向了一个对象的地址那我 *p本质是先拿到这个对象才能访问里面的成员变量。但p指向的对象的地址是一个空的地址(所以*p这一步正常来说会引发错误)但由于编译器的优化没出现错误。 那怎么才会出现错误呢 接下来再考一题. 2.下面程序编译运行结果是 A、编译报错 B、运行崩溃 C、正常运行 class A
{
public:void PrintA() {cout_aendl;//相当于this-_a}
private:int _a;
};
int main()
{A* p nullptr;p-PrintA();return 0;
} 答案:B 解析:由上面的第1题我们可知this-_a其实是p-_a,就相当于(*p)._a 对空指针p解引用并打印对象里的成员变量_a引发程序崩溃。 3. this指针存在哪里 this存在对象里面? 这是错误的 那它存在哪个区呢栈 堆 静态区 常量区 this是一个形参一般是存在栈帧里面vs下面一般会用ecx寄存器直接传递所以存在栈区 4.this指针可以为空吗 可以. C语言和C实现Stack的对比 C语言实现 可以看到在用C语言实现时Stack相关操作函数有以下共性 每个函数的第一个参数都是Stack*函数中必须要对第一个参数检测因为该参数可能会为NULL函数中都是通过Stack*参数操作栈的调用时必须传递Stack结构体变量的地址 结构体中只能定义存放数据的结构操作数据的方法不能放在结构体中即数据和操作数据 的方式是分离开的而且实现上相当复杂一点涉及到大量指针操作稍不注意可能就会出 错。 C实现 C中通过类可以将 数据 以及 操作数据的方法 进行完美结合通过访问权限可以控制那些方法在 类外可以被调用即封装在使用时就像使用自己的成员一样更符合人类对一件事物的认知。 而且每个方法不需要传递Stack*的参数了编译器编译之后该参数会自动还原即C中 Stack * 参数是编译器维护的(this指针)C语言中需用用户自己维护。 本章完。 本文修改次数0 更新时间2024年1月22日