【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( )这个成员函数的地址没有存到对象里面,因此能够正常编译。

相关文章
|
3天前
|
存储 编译器 C语言
c++的学习之路:5、类和对象(1)
c++的学习之路:5、类和对象(1)
19 0
|
3天前
|
C++
c++的学习之路:7、类和对象(3)
c++的学习之路:7、类和对象(3)
19 0
|
2天前
|
设计模式 Java C++
【C++高阶(八)】单例模式&特殊类的设计
【C++高阶(八)】单例模式&特殊类的设计
|
2天前
|
编译器 C++
【C++基础(八)】类和对象(下)--初始化列表,友元,匿名对象
【C++基础(八)】类和对象(下)--初始化列表,友元,匿名对象
|
6天前
|
存储 安全 C语言
【C++】string类
【C++】string类
|
存储 编译器 Linux
标准库中的string类(中)+仅仅反转字母+字符串中的第一个唯一字符+字符串相加——“C++”“Leetcode每日一题”
标准库中的string类(中)+仅仅反转字母+字符串中的第一个唯一字符+字符串相加——“C++”“Leetcode每日一题”
|
8天前
|
编译器 C++
标准库中的string类(上)——“C++”
标准库中的string类(上)——“C++”
|
8天前
|
编译器 C++
自从学了C++之后,小雅兰就有对象了!!!(类与对象)(中)——“C++”
自从学了C++之后,小雅兰就有对象了!!!(类与对象)(中)——“C++”
|
28天前
|
存储 C++ 容器
C++入门指南:string类文档详细解析(非常经典,建议收藏)
C++入门指南:string类文档详细解析(非常经典,建议收藏)
37 0
|
28天前
|
存储 编译器 C语言
C++入门: 类和对象笔记总结(上)
C++入门: 类和对象笔记总结(上)
33 0