• C++笔记:类和对象(一)->封装


    类和对象

            认识类和对象

             先来回忆一下C语言中的类型和变量,类型就像是定义了数据的规则,而变量则是根据这些规则来实际存储数据的容器。类是我们自己定义的一种数据类型,而对象则是这种数据类型的一个具体实例。类就可以理解为类型,而对象就可以理解为变量,而且对象是更加高级的变量。

            类型 = 类型数据 + 类型操作

            比如double类型

            他的数据类型占用8字节,并且可以表示成小数。

            类型操作可以加减乘除,但是它不支持模(%)运算。

            那么类就是包含了类型数据和类型操作的一个集合。

            也可以理解为数据就是类的属性,操作就是类的行为

            通过下面我定义的一个人的类,那么height,sex, age都是这个类的类型数据,而类型操作就是函数run和函数speak。

    1. class People {
    2. double height;//身高
    3. char sex;//性别
    4. int age;//年龄
    5. string name;//姓名
    6. void run() {//跑
    7. cout << "I can run" << endl;
    8. }
    9. void speak() {//说话
    10. cout << "I can speak" << endl;
    11. }
    12. };

            那么我需要一个特定的人的时候,我就需要定义一个对象用来存这个特定人的属性。

            下面的用类定义一个对象,并且对他的属性进行赋值。

    1. People a;
    2. a.name = "Zhang san";
    3. a.age = 18;
    4. a.sex = '1';//女0,男1
    5. a.height = 1.89;

            而我需要这个人进行说话或者跑时,就需要调用他相应的操作,也就是函数。

    1. a.run();
    2. a.speak();

            访问权限

            public:是公共的资源,是谁都可以访问的资源,比如空气,阳光,公共场合都是谁都可以访问进行使用的。

            private:是私有的资源,是只能类自己进行访问的资源,比如每个人自己的私人物品,是只能自己使用和访问的。

            protected:是受保护的资源,是只能自己访问以及自己的子类访问的资源,比如你儿子可以使用你的东西。(在后续文章继承时会用到protected权限)

            friendly:是在关键字friend声明后,它允许被声明后的类或函数访问这一个类的私有或受保护的资源,比如你的好兄弟在你的允许下可以访问和使用你的私人物品。(在后续的文章中会更新到使用)

            重点:这里的访问权限是规定的类外对该类类内的访问权限。

            比如我在一个类中定义了一个访问权限为私有的方法,那么我只能在这个类的内部进行调用,在类外是无法调用的,除非我对这个方向进行了friend友元声明。

            对于为什么要为什么要有访问权限的设置,看如下代码:

    1. #include
    2. using namespace std;
    3. class People {
    4. public :
    5. int age;
    6. void speak() {
    7. cout << "my age is "<< age << endl;
    8. return ;
    9. }
    10. };
    11. int main() {
    12. People a;
    13. a.age = -100;
    14. a.speak();
    15. return 0;
    16. }

            如果对于age是public公有访问的,那么在设置年龄的将不会受到限制,对于这份代码可能没有问题,如果在以后设计项目或者工程时,就会造成灾难性的错误。

            那么正确的写法应该如下,把age设置为私有的,对age赋值时,定义一个函数来进行对age赋值,并判断值是否在正确范围内,对于获取age值也定义一个函数进行来获取。

    1. #include
    2. using namespace std;
    3. class People {
    4. public :
    5. void speak() {
    6. cout << "my age is "<< age << endl;
    7. return ;
    8. }
    9. void set_age(int x) {
    10. if (age <= 0 || age > 1000) {
    11. perror("your age is error\n");
    12. exit(1);
    13. }
    14. age = x;
    15. return ;
    16. }
    17. int get_age() {
    18. return age;
    19. }
    20. private :
    21. int age;
    22. };
    23. int main() {
    24. People a;
    25. a.set_age(-100);
    26. a.speak();
    27. return 0;
    28. }

    构造函数和析构函数

            定义一个对象的流程:

         对于构造函数的详细解析如下代码:

    1. #include
    2. using namespace std;
    3. class A {
    4. public :
    5. A() {//默认构造函数
    6. cout << "default constructor" << endl;
    7. }
    8. //如果这里没有const关键字,传入的对象是const声明的那么就会报错
    9. //如果没有&(引用),那么将会造成无限套娃的拷贝构造
    10. //假如这里没有&,那么这里的形参就相当于实参给他赋值
    11. //而赋值就是 A a = b, b是实参传入的值
    12. //那这里就会又调用构造函数,形成无限套娃
    13. A(const A &a) {
    14. this->x = a.x;
    15. this->y = a.y;
    16. cout << "copy constructor" << endl;
    17. }
    18. A(int x, int y) {//有参构造函数
    19. cout << "have parameter constructor" << endl;
    20. this->x = x;//this表示当前对象的地址
    21. this->y = y;
    22. }
    23. void operator=(const A &a) {//这个函数是=运算符重载,是类中默认有的
    24. cout << "this is assigment" << endl;
    25. return ;
    26. }
    27. A(int x) {//转换构造函数
    28. cout << "conversion constructor" << endl;
    29. this->x = x;
    30. this->y = 0;
    31. }
    32. void output() {
    33. cout << "(" << x << ", " << y << ")" << endl;
    34. }
    35. ~A() {//析构函数
    36. cout << "destructor" << endl;
    37. }
    38. private :
    39. int x, y;
    40. };
    41. void func(A a) {
    42. cout << "func : ";
    43. a.output();
    44. return ;
    45. }
    46. int main() {
    47. A a;//调用默认构造函数
    48. cout << "1-------------------------" << endl;
    49. A b(1, 2);//调用有参构造
    50. cout << "2-------------------------" << endl;
    51. //这里就调用了拷贝构造函数
    52. A e = b;
    53. cout << "3-------------------------" << endl;
    54. //这里只是运用了普通赋值运算符
    55. //通过执行会发现没有执行任何构造函数
    56. a = b;
    57. cout << "4-------------------------" << endl;
    58. //调用转换构造,将int类型转换为A类型
    59. //这里也可以是另一个类型的对象,那也是转换构造
    60. //不过还需要从新定义一个需要转换构造的函数,里面的参数就是对应的类型
    61. A c(3);
    62. cout << "5-------------------------" << endl;
    63. //这里调用的是拷贝构造函数,将对象b进行拷贝给函数形参a
    64. func(b);
    65. func(c);
    66. cout << "6-------------------------" << endl;
    67. //对于这中函数调用情况
    68. //这里就会用到转换构造
    69. //他会隐式的将int类型5通过转换构造转会为A类型
    70. func(5);
    71. cout << "7-------------------------" << endl;
    72. //这里相当于A d(4);
    73. //这里可以这样理解
    74. //等号类型相同编程才能通过
    75. //那么4,int类型就会通过转换构造变成A类型
    76. //如果没有转换构造那么就编译无法通过
    77. A d = 4;
    78. a = 6;
    79. cout << "8-------------------------" << endl;
    80. return 0;
    81. }

    构造函数的初始化列表

    构造函数中的初始列表使用:

    1. #include
    2. using namespace std;
    3. class C {
    4. public:
    5. C(int n) {
    6. this->num = n;
    7. cout << "C coversion constructor" << endl;
    8. }
    9. void output() {
    10. cout << num;
    11. }
    12. private :
    13. int num;
    14. };
    15. class A {
    16. public :
    17. //:后面是构造函数的初始化列表
    18. A(int a) : a(a), b(10), c(6) {
    19. cout << "A constructor" << endl;
    20. }
    21. void output() {
    22. cout << " " <" " << b << " ";

    构造函数和析构函数的调用顺序

    如下这份代码:

    1. #include
    2. #include
    3. using namespace std;
    4. class A {
    5. public :
    6. A() {};
    7. A(string n) {
    8. name = n;
    9. cout << name << " class A construtor" << endl;
    10. }
    11. ~A() {
    12. cout << name << " class A destructor" << endl;
    13. }
    14. int *p;
    15. private :
    16. string name;
    17. };
    18. class B {
    19. public :
    20. //:a(a)
    21. //:后面是构造函数时,最该对象中的成员属性进行初始化操作
    22. B(string n, A &a) : a(a){
    23. name = n;
    24. cout << name << " class B construtor" << endl;
    25. //new关键字可以理解为,C语言中malloc去堆区中开辟一段空间
    26. a.p = new int[10];
    27. return ;
    28. }
    29. A &a;//创建一个引用属性,这里&a可以暂时理解为取a的地址
    30. ~B() {
    31. //delete就相当于C语言中的free对开辟空间的释放
    32. delete a.p;
    33. cout << name << " class B destructor" << endl;
    34. }
    35. private :
    36. string name;
    37. };
    38. int main() {
    39. A a("a");
    40. B b("b", a);
    41. return 0;
    42. }

    执行结果:

            可以发现,析构顺序是构造顺序反过来的,通过代码可以发现在B中的有参构造中用到了a对象的引用,然后对a对象的p指针开辟了一段空间,如果析构时顺序和构造顺序一样的那么,在析构b对象时,去delete a.p去释放开辟的空间时,a对象已经被析构掉了,那就也无法去找a对象中的指针p了。

    类属性与方法 

            对于上面的代码的类中,它们只有成员属性和成员方法。

            就拿文章中的第一份代码来看,对于属性,也就是类中的变量,他所存储的值是跟着对象改变而改变的,而类中的两个方法(函数),也是随着对象改变而改变的,因为它们的输出也是不同的。所以这些叫做成员属性和成员方法。

    1. class People {
    2. double height;//身高
    3. char sex;//性别
    4. int age;//年龄
    5. string name;//姓名
    6. void run() {//跑
    7. cout << name << " can run" << endl;
    8. }
    9. void speak() {//说话
    10. cout << name << " can speak" << endl;
    11. }
    12. };

     下面这份代码是对成员属性和成员方法的使用:

    1. #include
    2. using namespace std;
    3. class Point {
    4. public :
    5. Point() : x(0), y(0) {} //默认构造
    6. //区分x和y
    7. //在x(x),括号外是成员属性x,括号内是形参x
    8. Point(int x, int y) : x(x), y(y) {} //有参构造
    9. void set_x (int x) { this->x = x; }
    10. void set_y (int y) { this->y = y; }
    11. int get_x() {
    12. //访问类中的类属性
    13. Point::get_x_cnt += 1;
    14. return this->x;
    15. }
    16. int get_y() { return this->y; }
    17. //这里是直接定义了类方法
    18. static int x_cnt() {
    19. return Point::get_x_cnt;
    20. }
    21. private :
    22. //这里只是声明了这个变量
    23. //get_x_cnt,记录变量x在被get时,在整个程序执行时被调用了多少次
    24. static int get_x_cnt;
    25. int x, y;
    26. };
    27. //这里就是定义了get_x_cnt, 并且给他的值进行了初始化
    28. int Point::get_x_cnt = 0;
    29. int main() {
    30. Point p1(3, 4), p2(5, 6);
    31. cout << p1.get_x() << ", " << p1.get_y() << endl;
    32. cout << p2.get_x() << ", " << p2.get_y() << endl;
    33. p1.get_x();
    34. p1.get_x();
    35. p1.get_x();
    36. cout << "x_cnt : " << Point::x_cnt() << endl;
    37. return 0;
    38. }

     const 方法

    对于const和mutable关键字使用,对于上一份代码的修改:

    1. #include
    2. using namespace std;
    3. class Point {
    4. public :
    5. Point() : x(0), y(0) , get_x_cnt(0){} //默认构造
    6. Point(int x, int y) : x(x), y(y), get_x_cnt(0) {} //有参构造
    7. void set_x (int x) { this->x = x; }
    8. void set_y (int y) { this->y = y; }
    9. int get_x() const { //修改处
    10. get_x_cnt += 1;
    11. return this->x;
    12. }
    13. int get_y() const { return this->y; }//修改处
    14. int x_cnt() const {//修改处
    15. return get_x_cnt;
    16. }
    17. private :
    18. //现在get_x_cnt,表示当前对象对get_x函数的调用次数
    19. //mutable关键字表示不收const关键之的限制
    20. //所以就算有const关键字,那么mutable声明的变量也可以进行修改
    21. mutable int get_x_cnt;
    22. int x, y;
    23. };
    24. int main() {
    25. const Point p(10, 15), p2(1, 2);
    26. cout << p.get_x() << ", " << p.get_y() << endl;
    27. p2.get_x();
    28. p2.get_x();
    29. cout << "p1 : get_x_cnt = " << p.x_cnt() << endl;
    30. cout << "p2 : get_x_cnt = " << p2.x_cnt() << endl;
    31. return 0;
    32. }

    类的声明和定义

    还是对类属性和类方法的那份代码进行修改:

    1. #include
    2. using namespace std;
    3. //在类中只放方法的声明
    4. class Point {
    5. public :
    6. Point();
    7. Point(int x, int y);
    8. void set_x (int x);
    9. void set_y (int y);
    10. int get_x() const;
    11. int get_y() const;
    12. static int x_cnt();
    13. private :
    14. static int get_x_cnt;
    15. int x, y;
    16. };
    17. //对于类中的方法进行实现
    18. //那么就可以将这两部分拆开
    19. //定义放在源文件中,声明放在头文件中
    20. Point::Point() : x(0), y(0) {}
    21. Point::Point(int x, int y) : x(x), y(y) {}
    22. void Point::set_x(int x) {
    23. this->x = x;
    24. }
    25. void Point::set_y(int y) {
    26. this->y = y;
    27. }
    28. int Point::get_x() const{
    29. Point::get_x_cnt += 1;
    30. return x;
    31. }
    32. int Point::get_y() const{
    33. return y;
    34. }
    35. int Point::x_cnt() {
    36. return Point::get_x_cnt;
    37. }
    38. int Point::get_x_cnt = 0;
    39. int main() {
    40. Point p1(3, 4), p2(5, 6);
    41. cout << p1.get_x() << ", " << p1.get_y() << endl;
    42. cout << p2.get_x() << ", " << p2.get_y() << endl;
    43. p1.get_x();
    44. p1.get_x();
    45. p1.get_x();
    46. cout << "x_cnt : " << Point::x_cnt() << endl;
    47. return 0;
    48. }

    default和delete关键字

        delete和default关键字引用代码:

    1. #include
    2. using namespace std;
    3. class A {
    4. public :
    5. //A() = delete;
    6. A() {
    7. cout << "default constructor" << endl;
    8. }
    9. A(int n) : x(n) {
    10. cout << "conversion constructor" << endl;
    11. }
    12. A(const A &) {
    13. cout << "copy constructor" << endl;
    14. }
    15. private :
    16. int x;
    17. };
    18. class B {
    19. public :
    20. B() = default;
    21. B(const B&) = default;
    22. private :
    23. A a1, a2, a3;
    24. };
    25. class C {
    26. public :
    27. C() = default;
    28. //C(const C&) {}//这种情况叫撒比构造
    29. C(const C&) = default;
    30. private :
    31. A a1, a2, a3;
    32. };
    33. class Point {
    34. public :
    35. Point() = default;//请求编译器生成默认构造函数
    36. Point(int x, int y) : x(x), y(y) , p(new int(5)), a(1){}
    37. //Point(const Point &) = delete;//删除编译器的拷贝构造函数
    38. //Point(const Point &) = default;//拷贝函数生成默认行为,在使用这个拷贝构造函数时就会造成双重释放的报错
    39. //这就是深拷贝
    40. Point(const Point &a) : x(a.x + 1), y(a.y + 2), a(a.a){
    41. //如果这里成员属性a直接用这样去赋值拷贝
    42. //那么成员属性a就会使用A类的默认构造函数进行构造
    43. //而A类的默认构造函数已经被删除了,那么就会报错
    44. //this->a = a.a;
    45. this->p = new int;
    46. *(this->p) = *(a.p);
    47. }
    48. ~Point() {
    49. delete p;
    50. }
    51. private :
    52. A a;
    53. int *p;
    54. int x, y;
    55. };
    56. int main() {
    57. Point p1(1, 2), p2 = p1;
    58. return 0;
    59. }

     对象和引用

            

    代码演示:

    1. #include
    2. using namespace std;
    3. class A {
    4. public :
    5. A() = default;
    6. A(const A&) = default;
    7. private :
    8. };
    9. class B {
    10. public :
    11. B(A &a) : a(a) {}
    12. private :
    13. A &a;
    14. };
    15. class C {
    16. public :
    17. C() = default;
    18. C(const C &) = default;
    19. static void destroy(C *c) {
    20. delete c;
    21. return ;
    22. }
    23. private :
    24. ~C() {}
    25. };
    26. int main() {
    27. A a;
    28. B b(a);
    29. C *c = new C();
    30. C::destroy(c);
    31. return 0;
    32. }

    返回值优化

    先回顾一下一个对象是如何进行创建的:

    如下代码:

    1. #include
    2. using namespace std;
    3. class A {
    4. public :
    5. A() {
    6. cout << this << " default constructor" << endl;
    7. }
    8. A(const A &) {
    9. cout << this << " copy constructor" << endl;
    10. }
    11. string s;
    12. };
    13. A func() {
    14. //这里创建temp调用了默认构造
    15. A temp;
    16. cout << "temp = " << &temp << endl;
    17. temp.s = "hello func";
    18. //return这里会有一个匿名对象
    19. //然后temp会通过拷贝构造拷贝给这个匿名对象
    20. return temp;
    21. }
    22. int main() {
    23. //然后这里的a通过拷贝构造,拷贝这个匿名对象
    24. //那么创建a对象最终会调用
    25. //一次默认构造,两次拷贝构造对吧
    26. A a = func();
    27. cout << "a = " << &a << endl;
    28. return 0;
    29. }

    执行结果:

    会发现结果只调用了一次默认构造,也就是在调用func函数时创建temp进行调用的默认构造,这就就是编译器进行了返回值优化,可以看到对象a的地址和对象的temp的地址一摸一样都没变,说明中间的这些拷贝过程就是浪费资源。

    那么我在编译时关闭了,返回值优化,再看结果

    和我对于代码的分析是一样的。

    总结:

            对于上面的内容,只是我们在程序设计时需要掌握的知识点,应为没有掌握这些知识点,你也无法去实现你对应思想的代码。

            最后来说一下封装,这篇文章最主要的内容就是封装一个类,在下一篇文章C++类中的重载也是封装的一部分。而封装是什么,在设计代码的过程中,我们回去思考一个类他应该有什么属性,什么行为,然后我们就可以将这些属性和行为封装成为一个类。 对象是在程序运行中主要的逻辑,而类就是用来将对象进行分类的。不同的对象对于不同的类,就有不同的属性和行为,而这种代码设计逻辑就是封装。

            总的来说对于封装,可以理解为现实世界中的人和其他物种,我们具体的每个人就是对象,而人就想当于类,而类就去定义了我们的属性和行为,而不同的人有不同的行为和属性,那就是对象。人类就相当于封装了我们的属性和行为。所以在设计一个类去封装时我们需要考虑的就是他有什么属性,什么行为。

    下个内容:C++笔记:C++中的重载

  • 相关阅读:
    Redis常见命令
    iOS键盘通知弹框使用小结
    智能合约语言(eDSL)—— wasmtime实现合约引擎补充
    【嵌入式C语言】常见数据转化函数
    运维成本降低 50%,丽迅物流是如何应对大规模容器镜像管理挑战的
    【模型推理加速系列】06: 基于resnet18加速方案评测
    易基因|干货:手把手教你做RNA m5C甲基化测序分析(RNA-BS)
    若依框架解读(微服务版)—— 4.认证,登出(Gateway网关)
    数据库系统概念学习1
    【毕业设计】基于javaEE+SSH+mysql的码头船只出行及配套货柜码放管理系统设计与实现(毕业论文+程序源码)——码头船只出行及配套货柜码放管理系统
  • 原文地址:https://blog.csdn.net/qq_58240849/article/details/137831741