• C++基础语法——类和对象


    一、类的定义

    定义方法

    在c语言中的结构体,在c++中被叫做类,由于c++兼容c,因此由原先的定义方式是允许的,但在c++中,关于类经常使用的关键字不是struct,而一般用class,而且区别于以前的结构体,在类中不只可以有成员的声明,还可以直接写成员函数,或者是成员函数的声明

    1. class className
    2. {
    3. // 类体:由成员函数和成员变量组成
    4. }; // 一定要注意后面的分号

    例如:

    1. //写一个简单的类
    2. class Data
    3. {
    4. //私有的一般写成员声明
    5. private:
    6. int _year;//成员声明的变量一般在命名前加个_是为了区别成员函数的形参
    7. int _month;
    8. int _day;
    9. public:
    10. void Init(int year, int month, int day)//类的成员函数可以直接在类里面定义
    11. {
    12. _year = year;
    13. _month = month;
    14. _day = day;
    15. }
    16. void Print()
    17. {
    18. cout << _year << "年";
    19. cout << _month << "月";
    20. cout << _day << "日" << endl;
    21. }
    22. };
    23. int main()
    24. {
    25. Data n1;
    26. n1.Init(2023,07,31);
    27. n1.Print();
    28. system("pause");
    29. return 0;
    30. }

    访问限定符

    上面的例子中涉及到一个语法知识叫访问限定符

    访问限定符有三种:public(公用)、protected(保护)、private(私有)

    (目前暂时认为保护和私有是一样的)

    一般在类里要声明哪一部分是公用的,哪一部分是私有的,公用部分是可以在外部接口调用或者改变的部分,一般是成员函数,私有的则一般是成员声明或者不希望被改变的部分

    成员函数的声明与定义分离

    在定义类时,除了像上面的直接将定义写到类里,通常更多的是将声明放在类中,函数的定义放在另一个文件中,这里就涉及到一个类的作用域的概念,每一个类都是相对独立的一个域,在定义部分要在函数名前用域作用符去找到相对应的类作用域

    例子:

    1. class Person
    2. {
    3. public:
    4. void PrintPersonInfo();
    5. private:
    6. char _name[20];
    7. char _gender[3];
    8. int _age;
    9. };
    10. // 这里需要指定PrintPersonInfo是属于Person这个类域
    11. void Person::PrintPersonInfo()
    12. {
    13. cout << _name << " "<< _gender << " " << _age << endl;

    二、类对象模型

    计算类的大小

    类中的成员函数不被计算在类的大小中,它被放到另一块公共区域中,类的大小同样遵循着和结构体大小计算的对齐原则,只算成员声明部分,当类是一个空类或者没有成员时,则默认为1,用于占位

    this指针

    this指针是在调用类函数的过程中,经过编译器的处理,将对象的地址作为隐藏的形参传到函数中,使得类函数能找到相对应的对象进行操作

    1. this指针的类型:类类型* const,即成员函数中,不能给this指针赋值。

    2. 只能在“成员函数”的内部使用

    3. this指针本质上是“成员函数”的形参,当对象调用成员函数时,将对象地址作为实参传递给 this形参。所以对象中不存储this指针。

    4. this指针是“成员函数”第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递

    三、类的默认成员函数(重要)

    1.构造函数

    构造函数为了解决对象成员的初始化问题,在定义类时,会默认生成构造函数,默认生成的构造函数对内置类型不做处理,对自定义类型会去调用其相对应的构造函数,构造函数也可以自己定义

    构造函数还存在初始化列表,初始化列表用于每个独立的对象成员单独的初始化,所有的对象成员都在初始化列表上进行独立的初始化,定义方式如下

    1. class 类名
    2. {
    3. public:
    4. 类名()//构造函数
    5. :对象成员名(初始化的值),
    6. ...//可初始化多个,用逗号分割
    7. {函数体}
    8. private:
    9. 对象成员的声明;
    10. }
    11. 例子:
    12. class A
    13. {
    14. public:
    15. A()
    16. :_a(1),//初始化列表
    17. _b(2),
    18. _c(3)
    19. {
    20. //函数体
    21. }
    22. private:
    23. int _a;
    24. int _b;
    25. int _c;
    26. }

    初始化列表中进行初始化时,会优先采用括号内的值,当括号内不给值或者不进行显式的初始化时,会默认进行初始化,采用缺省值,当缺省值不存在时,遵循自定义类型会调用相关的构造函数,内置类型不做处理

    注意:由于一些特别情况,建议每次创建类时,每个对象成员都在初始化列表进行初始化

    其特征如下:

    1. 函数名与类名相同。

    2. 无返回值。

    3. 对象实例化时编译器自动调用对应的构造函数。

    4. 构造函数可以重载。

    5. 对象成员的初始化顺序按照声明的顺序来定,与初始化列表的顺序无关。

    2.析构函数

    析构函数用于自动处理类对象的销毁,默认生成的会对内置类型进行销毁,对自定义类型去调用其本身的析构函数,在一些情况下,用默认生成的析构函数即可,但如果涉及内存操作和管理的类,需要根据具体情况,自己去定义析构函数

    析构函数的定义:

    1. ~类名()
    2. {
    3.     //函数体;
    4. }

    析构函数是特殊的成员函数,其特征如下:

    1. 析构函数名是在类名前加上字符 ~。

    2. 无参数无返回值类型。

    3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构 函数不能重载

    4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。

    3.拷贝构造

    拷贝构造也是构造函数的一种,它主要解决对象的拷贝问题,默认生成的拷贝构造对内置类型不做处理,对自定义类型回去调用对于的拷贝构造,在自定义拷贝构造函数时,参数要传const &类型,不然会陷入死递归

    拷贝构造函数也是特殊的成员函数,其特征如下:

    1. 拷贝构造函数是构造函数的一个重载形式。

    2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错, 因为会引发无穷递归调用。

    3. 若未显式定义,编译器会生成默认的拷贝构造函数。

    4. 编译器生成的默认拷贝构造函数只能进行浅拷贝(值拷贝)

    5. 拷贝构造函数典型调用场景:

    使用已存在对象创建新对象

    函数参数类型为类类型对象

    函数返回值类型为类类型对象

    1. class Date
    2. {
    3. public:
    4. Date(int year, int minute, int day)//构造函数
    5. {
    6. cout << "Date(int,int,int):" << this << endl;
    7. }
    8. Date(const Date& d)//拷贝构造
    9. {
    10. cout << "Date(const Date& d):" << this << endl;
    11. }
    12. ~Date()//析构函数
    13. {
    14. cout << "~Date():" << this << endl;
    15. }
    16. private://成员对象
    17. int _year;
    18. int _month;
    19. int _day;
    20. };
    21. Date Test(Date d)//拷贝构造
    22. {
    23. Date temp(d);//拷贝构造
    24. return temp;//拷贝构造
    25. }
    26. int main()
    27. {
    28. Date d1(2022,1,13);//构造
    29. Test(d1);
    30. return 0;
    31. }

    四.运算符重载 operator

    函数原型:返回值类型 operator操作符(参数列表)

    作用:运算符重载是针对自定义类型在使用运算符时,可以对运算符进行重新定义,使得自定义类型也能根据实际需求去使用运算符,使用的规则也是自己定义

    运算符重载可以定义在全局使用,但更多时候是类内部进行定义

    不能通过连接其他符号来创建新的操作符:比如operator@

    重载操作符必须有一个类类型参数 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义

    作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this

    注意:     .*    ::    sizeof    ? :   .    这5个运算符不能重载。

    五、const成员

    定义:const可以用来修饰类成员函数,实际上修饰的是成员函数内的this指针,表明被修饰的成员函数内,不可对成员参数进行任何修改,将const修饰的“成员函数”称之为const成员函数

    使用方式:在成员函数后面直接加const,例: void fun(...) const;

    作用:在调用类成员函数时,有时候穿参时会使用 const对参数进行修饰,而this指针由于被隐藏,因此可能会存在权限发大的问题,使用该语法是用来补充这点的不足

    六、explicit关键字

    介绍:对于单参数的构造函数,具有类型转换的功能,在定义时可以自动的进行类型转换,若是不想其自动转换,可以使用explicit关键字去限制,关键字加在函数声明前

    七、static成员

    概念:

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

    特性:

    1.静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区,只进行一次初始化

    2.静态成员的定义必须在类外面,定义在全局,定义时不加关键字,用类名+: :去找到声明

    3.类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问

    4.静态成员函数没有隐藏的this指针,不能访问任何非静态成员

    5. 静态成员也是类的成员,受public、protected、private 访问限定符的限制

    应用场景之一:判断程序运行后类调用了多少次析构

    要判断调用多少次析构,可以在类中定义一个静态变量,对每次调用进行统计

    八、友元

    1.友元函数

    概念:

    由于类的封装特性,一般情况下,类外面的函数无法改变和调用类成员参数,在一些特殊的情况下,可能需要调用或者改变类成员参数时,可以在类内对该函数进行友元声明:

    格式:     

    friend 函数声明;

    (一般不建议使用友元函数,因为会破坏类的封装性)

    特性:

    友元函数可访问类的私有和保护成员,但不是类的成员函数

    友元函数不能用const修饰

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

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

    友元函数的调用与普通函数的调用原理相同

    2.友元类

    概念:

    与友元函数类似,在一个类中对另一个类进行友元声明,则被声明的类即可使用该类的类成员变量

    特性:

    1.友元关系是单向的,不具有交换性。

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

    2.友元关系不能传递 如果C是B的友元, B是A的友元,则不能说明C时A的友元。

    九、部分零零散散的小语法

    1.内部类

    概念:在类的内部也可以定义一个类,在内部的类可以直接使用外部的成员变量,相当于是外部类的友元类,但外部类不能直接使用内部的

    2.匿名对象

    概念:在一些情况下需要通过类对象去调用某些函数或者成员作返回值,但又不需要用到该对象本身时,可以使用匿名对象去调用,能够提高效率,匿名对象的生命周期只存在与定义那一行,用完即立刻销毁

    3.拷贝时编译器的一些自动优化

    概念:在拷贝构造时,如果存在一些重复多余的拷贝构造,则编译器可能会自动帮你优化

    例子:

    1. class A
    2. {
    3. public:
    4. A(int a = 0)
    5. :_a(a)
    6. {
    7. cout << "A(int a)" << endl;
    8. }
    9. A(const A& aa)
    10. :_a(aa._a)
    11. {
    12. cout << "A(const A& aa)" << endl;
    13. }
    14. 比特就业课
    15. A& operator=(const A& aa)
    16. {
    17. cout << "A& operator=(const A& aa)" << endl;
    18. if (this != &aa)
    19. {
    20. _a = aa._a;
    21. }
    22. return *this;
    23. }
    24. ~A()
    25. {
    26. cout << "~A()" << endl;
    27. }
    28. private:
    29. int _a;
    30. };
    31. void f1(A aa)
    32. {}
    33. A f2()
    34. {
    35. A aa;
    36. return aa;
    37. }
    38. int main()
    39. {
    40. // 传值传参
    41. A aa1;
    42. f1(aa1);
    43. cout << endl;
    44. // 传值返回
    45. f2();
    46. cout << endl;
    47. // 隐式类型,连续构造+拷贝构造->优化为直接构造
    48. f1(1);
    49. // 一个表达式中,连续构造+拷贝构造->优化为一个构造
    50. f1(A(2));
    51. cout << endl;
    52. // 一个表达式中,连续拷贝构造+拷贝构造->优化一个拷贝构造
    53. A aa2 = f2();
    54. cout << endl;
    55. // 一个表达式中,连续拷贝构造+赋值重载->无法优化
    56. aa1 = f2();
    57. cout << endl;
    58. return 0;
    59. }

    总结:

    本篇内容讲了关于类和对象的部分比较常用的语法,相比于c,在语法上有许多扩展,这些语法在结合具体的例子能更好的理解,建议可以自定义一个日期类进行练习,日期类细节上的搭建也会后续进行补充更新

  • 相关阅读:
    git clone:SSL: no alternative certificate subject name matches target host name
    数据结构与算法之美代码:排序算法3
    蓝牙芯片|瑞萨和TI推出新蓝牙芯片,试试伦茨科技ST17H65蓝牙BLE5.2芯片
    mmcv视频处理,如何遇到异常帧不中断
    Git常见命令快速参考
    如何使用 Lazydocker TUI 从终端管理 Docker
    pg ash自制版 pg_active_session_history
    quarkus数据库篇之三:单应用同时操作多个数据库
    Windows环境下的ELK——搭建环境(1)
    SpringBoot集成MyBatis(iBatis)
  • 原文地址:https://blog.csdn.net/china_chk/article/details/132017422