• C++类与对象常考知识


    目录

    α.友元

    1.友元函数

    2.友元类

    β.静态成员(static)

    γ.程序的内存模型

    内存分区模型

    1.程序运行前

    2.程序运行后

    练习巩固

    δ.new / delete 操作

    1.基本语法

    2.初始化new数组的问题

    3.new和delete操作自定义类型


    α.友元

    友元分为:友元函数友元类
    友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多用。



    1.友元函数

    友元函数 可以 直接访问 类的 私有 成员,它是 定义在类外部 普通函数 ,不属于任何类,但需要在类的内部声明,声明时需要加friend 关键字。
    1. #include <iostream>
    2. using namespace std;
    3. class Date
    4. {
    5. friend ostream& operator<<(ostream& _cout, const Date& d);
    6. friend istream& operator>>(istream& _cin, Date& d);
    7. public:
    8. Date(){}
    9. Date(int year, int month, int day)
    10. : _year(year)
    11. , _month(month)
    12. , _day(day)
    13. {}
    14. private:
    15. int _year;
    16. int _month;
    17. int _day;
    18. };
    19. ostream& operator<<(ostream& _cout, const Date& d) {
    20. _cout << d._year << "-" << d._month << "-" << d._day;
    21. return _cout;
    22. }
    23. istream& operator>>(istream& _cin, Date& d) {
    24. _cin >> d._year;
    25. _cin >> d._month;
    26. _cin >> d._day;
    27. return _cin;
    28. }
    29. int main()
    30. {
    31. Date d;
    32. cin >> d;
    33. cout << d << endl;
    34. return 0;
    35. }

    注意事项:

    ① 友元函数可以访问类的 private 和 protected 成员,但并不代表能访问类的成员函数。

    ② 友元函数不能用 const 修饰。

    ③ 友元函数可以在类定义的任何地方申明,可以不受类访问限定符的控制。

    ④ 一个函数可以是多个类的友元函数。

    ⑤ 友元函数的调用和普通函数的调用原理相同。

    2.友元类

    友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
    • 友元关系是单向的,不具有交换性:

            比如下文Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接访问Time类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。

    • 友元关系不能传递:

            如果B是A的友元,C是B的友元,则不能说明C时A的友元。

    一定要前置类声明!! 

    1. #include <iostream>
    2. using namespace std;
    3. class Date; // 前置声明
    4. class Time
    5. {
    6. friend class Date; // 声明日期类为时间类的友元类,则在日期类中就直接访问Time类中的私有成员变量
    7. public:
    8. Time() {}
    9. Time(int hour, int minute, int second)
    10. : _hour(hour)
    11. , _minute(minute)
    12. , _second(second)
    13. {}
    14. private:
    15. int _hour;
    16. int _minute;
    17. int _second;
    18. };
    19. class Date
    20. {
    21. public:
    22. Date(int year = 1900, int month = 1, int day = 1)
    23. : _year(year)
    24. , _month(month)
    25. , _day(day)
    26. {}
    27. void SetTimeOfDate(int hour, int minute, int second)
    28. {
    29. // 直接访问时间类私有的成员变量
    30. _t._hour = hour;
    31. _t._minute = minute;
    32. _t._second = second;
    33. }
    34. private:
    35. int _year;
    36. int _month;
    37. int _day;
    38. Time _t;
    39. };
    40. int main() {
    41. return 0;
    42. }

    这里 Date 是 Time 的友元,我们在日期类里就可以访问时间类的私有成员了。

    但是时间类里不能访问日期类,因为这是 "单向好友" ,如果想在时间类里访问日期类,我们可以在日期类里声明:

    1. class Date {
    2. friend class Time;
    3. // ...
    4. }

    这样,它们之间就是 "双向好友" 了 —— 互相成为对方的友元。

    β.静态成员(static)

    复习:所有对象共享同一份数据;编译阶段就分配内存;类内声明类外初始化; 静态成员函数只能访问静态成员变量;

    声明为 static 的类成员称为类的静态成员,用 static 修饰的成员变量,称为静态成员变量。

    用 static 修饰的成员函数,称为静态成员函数,静态的成员变量一定要在类外进行初始化。

    1. class A {
    2. public:
    3. A() { ++_scount; }
    4. A(const A& t) { ++_scount; }
    5. static int GetACount() { return _scount; }
    6. private:
    7. static int _scount;
    8. };
    9. int A::_scount = 0;
    10. void TestA()
    11. {
    12. cout << A::GetACount() << endl;
    13. A a1, a2;
    14. A a3(a1);
    15. cout << A::GetACount() << endl;
    16. }

    ① 静态成员为所有类对象所共享,不属于某个具体的实例。

    ② 静态成员变量必须在类外定义,定义时不添加 static 关键字。

    ③ 类静态成员即可用类名 :: 静态成员变量或者对象 . 来访问。

    ④ 静态成员函数没有隐藏的 this 指针,不能访问任何非静态成员,static函数唯一能够访问的就是static变量或者其他static函数

    ⑤ 静态成员和类的普通成员一样,也有 public、protected、private 三种访问级别,也可以具有返回值。
     

    注意:静态成员只会被初始化一次!

    γ.程序的内存模型

    内存分区模型

    C++程序在执行时,将内存大方向划分为4个区域

    • 代码区(代码段):存放函数体的二进制代码,由操作系统进行管理的
    • 全局区(数据段):存放全局变量和静态变量以及常量
    • 栈区:由编译器自动分配释放, 存放函数的参数值,局部变量等
    • 堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收

    代码段(code segment)

            可执行的代码 / 只读常量。代码段存放类成员函数和全局函数的二进制代码。

    一个程序起来之后,会把它的空间进行划分,而划分是为了更好地管理。

    函数调用,函数里可能会有很多变量,函数调用建立栈帧,栈帧里存形参、局部变量等等

    全局区(data segment)

            静态存储区,数据段存放全局变量和静态数据,程序结束后由系统释放。

    栈区(stack)

     栈又叫堆栈,非静态局部变量/函数参数/返回值等等,栈是向下增长的。

    执行函数时,函数内部局部变量的存储单元都可以在栈上创建。

    函数执行结束后这些存储单元会被自动释放。栈内存分配运算内置于处理器的指令集中,

    拥有很高的效率,但是分配的内存容量是有限的。

    栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。
     

    堆区(heap)

            堆用于程序运行时动态内存分配,堆是可以上增长的。

    一般由程序员自主分配释放,若程序员不主动不释放,程序结束时可能由操作系统回收。

    其分配方式类似于链表。

    1.程序运行前

    在程序编译后,生成了exe可执行程序,未执行该程序前分为两个区域

    ​ 代码区:

    ​ 存放 CPU 执行的机器指令

    ​ 代码区是共享的,共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可

    ​ 代码区是只读的,使其只读的原因是防止程序意外地修改了它的指令

    ​ 全局区:

    ​ 全局变量和静态变量存放在此.

    ​ 全局区还包含了常量区, 字符串常量和其他常量也存放在此.

    ​ 该区域的数据在程序结束后由操作系统释放.

    举个栗子:

    1. #include <iostream>
    2. using namespace std;
    3. //全局变量
    4. int g_a = 10;
    5. int g_b = 10;
    6. //全局常量
    7. const int c_g_a = 10;
    8. const int c_g_b = 10;
    9. int main() {
    10. //局部变量
    11. int a = 10;
    12. int b = 10;
    13. //打印地址
    14. cout << "局部变量a地址为: " << &a << endl;
    15. cout << "局部变量b地址为: " << &b << endl;
    16. cout << "全局变量g_a地址为: " << &g_a << endl;
    17. cout << "全局变量g_b地址为: " << &g_b << endl;
    18. //静态变量
    19. static int s_a = 10;
    20. static int s_b = 10;
    21. cout << "静态变量s_a地址为: " << &s_a << endl;
    22. cout << "静态变量s_b地址为: " << &s_b << endl;
    23. cout << "字符串常量地址为: " << &"hello world" << endl;
    24. cout << "字符串常量地址为: " << &"hello world1" << endl;
    25. cout << "全局常量c_g_a地址为: " << &c_g_a << endl;
    26. cout << "全局常量c_g_b地址为: " << &c_g_b << endl;
    27. const int c_l_a = 10;
    28. const int c_l_b = 10;
    29. cout << "局部常量c_l_a地址为: " << &c_l_a << endl;
    30. cout << "局部常量c_l_b地址为: " << &c_l_b << endl;
    31. system("pause");
    32. return 0;
    33. }

    输出:

     观察输出的地址,我们发现其中局部变量和局部常量地址为一坨,而其余地址为另一坨,得出如图结论;

    2.程序运行后

     栈区:

    ​ 由编译器自动分配释放, 存放函数的参数值,局部变量等

    ​ 注意事项:不要返回局部变量的地址,栈区开辟的数据由编译器自动释放

    1. #include<bits/stdc++.h>
    2. using namespace std;
    3. int * func()
    4. {
    5. int a = 10;
    6. return &a;
    7. }
    8. int main() {
    9. int *p = func();
    10. cout << *p << endl;
    11. cout << *p << endl;
    12. return 0;
    13. }

    堆区:

    ​ 由程序员分配释放,若程序员不释放,程序结束时由操作系统回收

    ​ 在C++中主要利用new在堆区开辟内存

    1. #include<bits/stdc++.h>
    2. using namespace std;
    3. int* func()
    4. {
    5. int* a = new int(10);
    6. return a;
    7. }
    8. int main() {
    9. int *p = func();
    10. cout << *p << endl;
    11. cout << *p << endl;
    12. return 0;
    13. }

    总结:

    1. 堆区数据由程序员管理开辟和释放
    2. 栈区​由编译器自动分配释放
    3. 堆区数据利用new关键字进行开辟内存(或者用malloc / calloc / realloc )

    练习巩固

    1. int globalVar = 1;
    2. static int staticGlobalVar = 1;
    3. void Test()
    4. {
    5. static int staticVar = 1;
    6. int localVar = 1;
    7. int num1[10] = { 1, 2, 3, 4 };
    8. char char2[] = "abcd";
    9. const char* pChar3 = "abcd";
    10. int* ptr1 = (int*)malloc(sizeof(int) * 4);
    11. int* ptr2 = (int*)calloc(4, sizeof(int));
    12. int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4);
    13. free(ptr1);
    14. free(ptr3);
    15. }
    16. 1. 选择题:
    17. 选项: A.栈 B.堆 C.数据段 D.代码段
    18. globalVar在哪里?____ staticGlobalVar在哪里?____
    19. staticVar在哪里?____ localVar在哪里?____
    20. num1 在哪里?____
    21. char2在哪里?____ *char2在哪里?___
    22. pChar3在哪里?____ *pChar3在哪里?____
    23. ptr1在哪里?____ *ptr1在哪里?____
    24. 2. 填空题:
    25. sizeof(num1) = ____;
    26. sizeof(char2) = ____; strlen(char2) = ____;
    27. sizeof(pChar3) = ____; strlen(pChar3) = ____;
    28. sizeof(ptr1) = ____;

      答案:CCCAA  AAADAB

    注意:

    1. #include<bits/stdc++.h>
    2. using namespace std;
    3. int main()
    4. {
    5. char char2[] = "abcd";
    6. const char* pChar3 = "abcd";
    7. cout<<&("abcd")<<endl;
    8. cout<<&(char2)<<endl;
    9. cout<<&(pChar3)<<endl;
    10. return 0;
    11. }

    字符串常量与局部常量的区别 

    δ.new / delete 操作

    C++中利用new操作符在堆区开辟数据

    ​ 堆区开辟的数据,由程序员手动开辟,手动释放,释放利用操作符 delete

    1.基本语法

    1. void Test_CPP() {
    2. // 动态申请一个int类型的空间
    3. int* p1 = new int;
    4. // 动态申请一个int类型的空间并初始化为10
    5. int* p2 = new int(10);
    6. // 动态申请10个int类型的空间
    7. int* p3 = new int[10];
    8. // 单个对象,delete即可。
    9. delete p1;
    10. delete p2;
    11. // 多个对象,delete[] 。
    12. delete[] p3;
    13. }
    注意:申请和释放单个元素的空间,使用 new delete 操作符,申请和释放连续的空间,使用 new[] delete[]

    2.初始化new数组的问题


     C++98 不支持初始化 new 数组:

    int* p = new int[5];//报错


     C++11 允许大括号初始化,我们就可以用 { } 列表初始化:

    1. int* p1 = new int[5]{1,2}         // 1 2 0 0 0
    2. int* p2 = new int[5]{1,2,3,4,5};  // 1 2 3 4 5

    3.newdelete操作自定义类型

    在申请自定义类型的空间时,new 会调用构造函数,

    delete 会调用析构函数,而 malloc 与 free 不会。

    new:在堆上申请空间 + 调用构造函数输出。

    delete:先调用指针类型的析构函数 + 释放空间给堆上。

    malloc 和 new 的对比 

    1. #include <iostream>
    2. #include<stdlib.h>
    3. using namespace std;
    4. class A {
    5. public:
    6. A()
    7. : _a(0) {
    8. cout << "A():" << this << endl;
    9. }
    10. ~A() {
    11. cout << "~A():" << this << endl;
    12. }
    13. private:
    14. int _a;
    15. };
    16. int main(void)
    17. {
    18. // 动态申请单个A对象和5个A对象数组
    19. A* p1 = (A*)malloc(sizeof(A));
    20. A* p2 = (A*)malloc(sizeof(A) * 5);
    21. //A* p3 = new A; // 后面只需要跟类型就可以
    22. //A* p4 = new A[5];
    23. }

    将new注释发现没有输出,使用new时如图:

    free 与 delete 的对比

    1. #include <iostream>
    2. #include<stdlib.h>
    3. using namespace std;
    4. class A {
    5. public:
    6. A()
    7. : _a(0) {
    8. cout << "A():" << this << endl;
    9. }
    10. ~A() {
    11. cout << "~A():" << this << endl;
    12. }
    13. private:
    14. int _a;
    15. };
    16. int main(void)
    17. {
    18. A* p1 = (A*)malloc(sizeof(A));
    19. A* p2 = (A*)malloc(sizeof(A) * 5);
    20. A* p3 = new A;
    21. A* p4 = new A[5];
    22. free(p1);
    23. free(p2);
    24. delete p3;
    25. delete[] p4;
    26. // ...
    27. }

    相对的,free 只是把 p1 p2 指向的空间释放掉。

    而 delete 不仅会释 p1 p2 指向的空间,delete 还会调用对应的析构函数。

    new的原理

            1. 调用operator new函数申请空间

            2. 在申请的空间上执行构造函数,完成对象的构造

    delete的原理

            1. 在空间上执行析构函数,完成对象中资源的清理工作

            2. 调用operator delete函数释放对象的空间

    new T[N]的原理

            1. 调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对象空间的申请

            2. 在申请的空间上执行N次构造函数

    delete[]的原理

            1. 在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理

            2. 调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释放空间

    γδεζηθ

  • 相关阅读:
    【案例 5-4】 字符串转换为二进制
    ubuntu cv2.imshow显示图片问题
    监控与升级
    深度学习:模型训练过程中Trying to backward through the graph a second time解决方案
    【C++心愿便利店】No.9---C++之内存管理
    【51单片机实验笔记】前篇(三) 模块功能封装汇总(持续更新)
    网络协议学习笔记
    React学习--- 事件处理
    前端研习录(26)——JavaScript DOM讲解及示例分析
    【2022-11-07】 转-代码质量保证
  • 原文地址:https://blog.csdn.net/qq_61386381/article/details/125119533