【C++】-- 类和对象(二)

简介: 【C++】-- 类和对象

五、类对象模型

1.如何计算类对象的大小

遵循内存对齐原则,Stack类的成员变量是 a、size、capacity,每个变量的类型都是int,因此Stack类的成员变量大小为3*4=12个字节。

1. int main()
2. {
3.  Stack st;
4.  cout << sizeof(st) << endl;
5. 
6.     return 0;
7. }

 但是,打印发现Stack类的的对象大小就是12,那成员函数没有计算大小吗?

2.类的对象存储方式

假如对象中包含类的各个成员,既包含成员变量,又包含成员函数,由于每个对象中成员变量是不同的,但调用的是同一份成员函数,如果按照此种方式存储,当一个类创建多个对象时,每个对象中都会保存一份成员函数代码,相同代码保存多次,会浪费空间。

因此C++采用类对象中只存储成员变量,成员函数存放在公共代码段中的方式来存储类的对象。

1. #include<iostream>
2. using namespace std;
3. 
4. class A1 {
5. public:
6.  void f1() {}
7. private:
8.  int _a;
9. };
10. 
11. // 类中仅有成员函数
12. class A2 {
13. public:
14.   void f2() {}
15. };
16. 
17. // 类中什么都没有---空类
18. class A3
19. {};
20. 
21. int main()
22. {
23.   A1 a1;
24.   A2 a2;
25.   A3 a3;
26. 
27.   cout << sizeof(a1) << endl;
28.   cout << sizeof(a2) << endl;
29.   cout << sizeof(a3) << endl;
30. 
31.   cout << &a1 << endl;
32.   cout << &a2 << endl;
33.   cout << &a3 << endl;
34. 
35.   return 0;
36. }

sizeof(a2) 和 sizeof(a3) 为什么都是1?编译器给空类一个字节来标识这是一个空类,这1个byte不是为了存储数据,是占位,表示对象存在过。尽管对象a2的类中没有成员变量仅有成员函数,对象a3的类中既没有成员变量也没有成员函数,但是它们的地址不同,表明对象a2和对象a3存在过。

六、this指针

当定义一个对象时,如何将对象初始化呢?C++把成员变量和成员函数封装到类里面,不想让成员变量随意被访问,需要通过公有的合法方式进行访问。下面的代码是无法给d1正确初始化的:

1. #include<iostream>
2. using namespace std;
3. 
4. class Date
5. {
6. public:
7.  void Init(int year, int month, int day)
8.  {
9.    year = year;
10.   }
11. 
12. private:
13.   int year;
14.   int month;
15.   int day;
16. };
17. 
18. int main()
19. {
20.   Date d1;
21.   //d1.year = 2022;//非合法访问,类外面无法访问private成员变量
22.   d1.Init(2021, 5, 20);
23. 
24.     return 0;
25. }

因为第9行year = year中的year都是第7行的入参year,因为所有的变量访问都遵循就近原则,类里面有两个变量year,一个是第8行的入参year,另一个是第13行的成员变量year,因此第9行代码中的赋值的两个year会优先访问第7行的入参year,都是第7行的入参year的值,把自己赋值给自己,没起任何作用。

虽然第9行可以指定域:

Date::year = year;

编译能够通过,但是写起来不方便,为此可以用命名风格进行区分,成员变量使用这样的风格:

1. private:
2.  int _year;
3.  int _month;
4.  int _day;

也可以使用其他风格,只要和入参能够区分开就可以。因此,代码可以这样写:

1. #include<iostream>
2. using namespace std;
3. 
4. class Date
5. {
6. public:
7.  void Init(int year, int month, int day)
8.  {
9.    _year = year;
10.     _month = month;
11.     _day = day;
12.   }
13. 
14. private:
15.   int _year;
16.   int _month;
17.   int _day;
18. };
19. 
20. int main()
21. {
22.   Date d1;
23.   d1.Init(2022, 4, 8);
24. 
25.     return 0;
26. }

F10监视对象d1的三个变量,发现被初始化成功了:

成员变量声明是没有为对象开辟空间的,只有定义了对象后,数据存储在对象里面

1. private:
2. //成员变量声明
3.  int _year;
4.  int _month;
5.  int _day;

如果定义两个对象,d1和d2如何访问自己的年月日呢?两个对象调用的是同一份成员函数,如何做到各自初始化各自的?

1. int main()
2. {
3.  Date d1;
4.  d1.Init(2022, 4, 8);
5. 
6.  Date d2;
7.  d2.Init(2022, 4, 9);
8. 
9.     return 0;
10. }

因为编译器做了处理,Init函数的入参不是3个参数,实际上是4个参数,编译器会增加一个隐含的参数:this指针,编译器会把对象的地址传给this。

1. int main()
2. {
3.  Date d1;
4.  d1.Init(2022, 4, 8);//实际为d1.Init(&d1,2022,4,8)
5. 
6.  Date d2;
7.  d2.Init(2022, 4, 9);//实际为d2.Init(&d2,2022,4,9)
8. 
9.     return 0;
10. }

编译器增加的隐含的this指针参数,即void Init(Date* this, int year, int month, int day),作为形参,this指针存在于栈中。

this指针是隐含的,是编译器编译时显示自动增加的,不能显式地在调用和函数定义中增加,在Date类中第9-11行加不加this访问成员变量都能成功初始化,可以在成员函数中使用this指针。

使用this指针打印两个对象各自的地址:

1. #include<iostream>
2. using namespace std;
3. 
4. class Date
5. {
6. public:
7.  void Init(int year, int month, int day)
8.  {
9.    _year = year;
10.     _month = month;
11.     _day = day;
12.     cout << "this:" << this << endl;
13.   }
14. 
15. private:
16.   int _year;
17.   int _month;
18.   int _day;
19. };
20. 
21. int main()
22. {
23.   Date d1;
24.   d1.Init(2022, 4, 8);
25.   cout << "d1:" << &d1 << endl;
26. 
27.   Date d2;
28.   d2.Init(2022, 4, 9);
29.   cout << "d2:" << &d2 << endl;
30. 
31.     return 0;
32. }

第一次调用Init,this的地址就是d1的地址,第二次调用Init,this的地址就是d2的地址:

为了加深对this指针的理解,可以看看下面代码:

1. #include<iostream>
2. using namespace std;
3. 
4. // 1.下面程序能编译通过吗?
5. // 2.下面程序会崩溃吗?在哪里崩溃
6. 
7. class A
8. {
9. public:
10.   void PrintA()
11.   {
12.     cout << _a << endl;
13.   }
14.   void Show()
15.   {
16.     cout << "Show()" << endl;
17.   }
18. 
19. private:
20.   int _a;
21. };
22. 
23. int main()
24. {
25.   A* p = nullptr;
26.   p->PrintA();
27.   p->Show();
28. }

程序在26行p->PrintA( );处崩溃,可以注掉第26行,就可以打印Show()了。

为什么访问p->PrintA( )会崩溃?因为p为空指针,第10行的成员函数PrintA()接收到的指针是空指针,访问this->_a对空指针解引用会崩溃。

p->Show( )没有对p这个指针解引用,因为Show( )这个成员函数的地址没有存到对象里面,因此能够正常编译。

相关文章
|
9月前
|
编译器 C++ 开发者
【C++篇】深度解析类与对象(下)
在上一篇博客中,我们学习了C++的基础类与对象概念,包括类的定义、对象的使用和构造函数的作用。在这一篇,我们将深入探讨C++类的一些重要特性,如构造函数的高级用法、类型转换、static成员、友元、内部类、匿名对象,以及对象拷贝优化等。这些内容可以帮助你更好地理解和应用面向对象编程的核心理念,提升代码的健壮性、灵活性和可维护性。
|
5月前
|
人工智能 机器人 编译器
c++模板初阶----函数模板与类模板
class 类模板名private://类内成员声明class Apublic:A(T val):a(val){}private:T a;return 0;运行结果:注意:类模板中的成员函数若是放在类外定义时,需要加模板参数列表。return 0;
158 0
|
5月前
|
存储 编译器 程序员
c++的类(附含explicit关键字,友元,内部类)
本文介绍了C++中类的核心概念与用法,涵盖封装、继承、多态三大特性。重点讲解了类的定义(`class`与`struct`)、访问限定符(`private`、`public`、`protected`)、类的作用域及成员函数的声明与定义分离。同时深入探讨了类的大小计算、`this`指针、默认成员函数(构造函数、析构函数、拷贝构造、赋值重载)以及运算符重载等内容。 文章还详细分析了`explicit`关键字的作用、静态成员(变量与函数)、友元(友元函数与友元类)的概念及其使用场景,并简要介绍了内部类的特性。
249 0
|
7月前
|
编译器 C++ 容器
【c++11】c++11新特性(上)(列表初始化、右值引用和移动语义、类的新默认成员函数、lambda表达式)
C++11为C++带来了革命性变化,引入了列表初始化、右值引用、移动语义、类的新默认成员函数和lambda表达式等特性。列表初始化统一了对象初始化方式,initializer_list简化了容器多元素初始化;右值引用和移动语义优化了资源管理,减少拷贝开销;类新增移动构造和移动赋值函数提升性能;lambda表达式提供匿名函数对象,增强代码简洁性和灵活性。这些特性共同推动了现代C++编程的发展,提升了开发效率与程序性能。
290 12
|
8月前
|
设计模式 安全 C++
【C++进阶】特殊类设计 && 单例模式
通过对特殊类设计和单例模式的深入探讨,我们可以更好地设计和实现复杂的C++程序。特殊类设计提高了代码的安全性和可维护性,而单例模式则确保类的唯一实例性和全局访问性。理解并掌握这些高级设计技巧,对于提升C++编程水平至关重要。
173 16
|
8月前
|
编译器 C++
类和对象(中 )C++
本文详细讲解了C++中的默认成员函数,包括构造函数、析构函数、拷贝构造函数、赋值运算符重载和取地址运算符重载等内容。重点分析了各函数的特点、使用场景及相互关系,如构造函数的主要任务是初始化对象,而非创建空间;析构函数用于清理资源;拷贝构造与赋值运算符的区别在于前者用于创建新对象,后者用于已存在的对象赋值。同时,文章还探讨了运算符重载的规则及其应用场景,并通过实例加深理解。最后强调,若类中存在资源管理,需显式定义拷贝构造和赋值运算符以避免浅拷贝问题。
|
8月前
|
存储 编译器 C++
类和对象(上)(C++)
本篇内容主要讲解了C++中类的相关知识,包括类的定义、实例化及this指针的作用。详细说明了类的定义格式、成员函数默认为inline、访问限定符(public、protected、private)的使用规则,以及class与struct的区别。同时分析了类实例化的概念,对象大小的计算规则和内存对齐原则。最后介绍了this指针的工作机制,解释了成员函数如何通过隐含的this指针区分不同对象的数据。这些知识点帮助我们更好地理解C++中类的封装性和对象的实现原理。
|
8月前
|
安全 C++
【c++】继承(继承的定义格式、赋值兼容转换、多继承、派生类默认成员函数规则、继承与友元、继承与静态成员)
本文深入探讨了C++中的继承机制,作为面向对象编程(OOP)的核心特性之一。继承通过允许派生类扩展基类的属性和方法,极大促进了代码复用,增强了代码的可维护性和可扩展性。文章详细介绍了继承的基本概念、定义格式、继承方式(public、protected、private)、赋值兼容转换、作用域问题、默认成员函数规则、继承与友元、静态成员、多继承及菱形继承问题,并对比了继承与组合的优缺点。最后总结指出,虽然继承提高了代码灵活性和复用率,但也带来了耦合度高的问题,建议在“has-a”和“is-a”关系同时存在时优先使用组合。
476 6
|
8月前
|
编译器 C++
类和对象(下)C++
本内容主要讲解C++中的初始化列表、类型转换、静态成员、友元、内部类、匿名对象及对象拷贝时的编译器优化。初始化列表用于成员变量定义初始化,尤其对引用、const及无默认构造函数的类类型变量至关重要。类型转换中,`explicit`可禁用隐式转换。静态成员属类而非对象,受访问限定符约束。内部类是独立类,可增强封装性。匿名对象生命周期短,常用于临时场景。编译器会优化对象拷贝以提高效率。最后,鼓励大家通过重复练习提升技能!
|
9月前
|
编译器 C语言 C++
类和对象的简述(c++篇)
类和对象的简述(c++篇)