• 对类和对象的详细解析


    目录

    1.类的构成

    2.this指针

     3.C++类里面的静态成员

     3.1 类里面的静态成员函数

    3.2 类里面的静态成员变量

    静态成员变量的特点

    共享性

    存储位置

    生命周期

    访问权限

    如何初始化?

     构造函数


    1.类的构成

     public修饰的成员在类外可以直接被访问

    private和protected修饰的成员不能在类外被访问

    如果变量没有被这三个修饰词来修饰的话,则默认为private

    2.this指针

    关于this指针的一个经典回答:

    有一个房子类

    当你进入这个房子的时候,你可以看见桌子、椅子等,但是房子的全貌你看不见了

    对于一个类的实例来说,你可以看见他的成员函数、成员变量

    但是实例本身呢?

    this这个指针就时时刻刻指向这个实例本身

    this指针是指向对象的指针,存放对象的首地址

    1. #include
    2. using namespace std;
    3. class Student
    4. {
    5. public:
    6. void Setage(int age)
    7. {
    8. this->age = age;//this->age表示这个类所指向的age,单独的age是形参
    9. }
    10. void Showage()
    11. {
    12. cout << age << endl;
    13. }
    14. private:
    15. int age;
    16. };
    17. int main()
    18. {
    19. Student s;
    20. s.Setage(18);
    21. s.Showage();
    22. return 0;
    23. }
    24. //输出结果为18
    1. #include
    2. using namespace std;
    3. class Student
    4. {
    5. public:
    6. void Setage(int age)
    7. {
    8. age = age;//age=age,并不会改变类里面age的值
    9. }
    10. void Showage()
    11. {
    12. cout << age << endl;
    13. }
    14. private:
    15. int age;
    16. };
    17. int main()
    18. {
    19. Student s;
    20. s.Setage(18);
    21. s.Showage();
    22. return 0;
    23. }
    24. //输出结果是0

    this指针是编译器帮我们加上的,不占用空间

    1. #include
    2. using namespace std;
    3. class Student
    4. {
    5. private:
    6. int age;
    7. };
    8. int main()
    9. {
    10. cout << sizeof(Student) << endl;//结果为4
    11. return 0;
    12. }

    既然this指针指向类,那么类的地址和this的地址是一样的

    1. #include
    2. using namespace std;
    3. class Student
    4. {
    5. public:
    6. void test()
    7. {
    8. cout << this << endl;
    9. }
    10. private:
    11. int age;
    12. };
    13. int main()
    14. {
    15. Student s;
    16. s.test();
    17. cout << &s << endl;
    18. return 0;
    19. }

    输出的地址是一样的

     3.C++类里面的静态成员

    静态成员是属于整个类的而不是某个对象,静态成员只存储一份供所有对象使用

     3.1 类里面的静态成员函数

    静态成员函数没有隐藏的this指针

    1. #include
    2. using namespace std;
    3. class Student
    4. {
    5. public:
    6. void Set(int age)
    7. {
    8. this->age = age;
    9. }
    10. void Showage()
    11. {
    12. cout << age << endl;
    13. }
    14. static void test()
    15. {
    16. cout<<this->age<//这个地方的age会报错,如果不加this也会报错
    17. //静态成员函数不能引用非静态成员,但是可以在类的非静态成员函数里面使用静态成员
    18. }
    19. private:
    20. int age;
    21. };
    22. int main()
    23. {
    24. Student s;
    25. return 0;
    26. }

    这就说明了静态函数是在这个类创建出来之前就出现了,也就是说静态函数的创建时间比这个类早,所以在这个静态函数里面使用类里面的东西会报错

    那么应该如何使用这个函数呢

    首先要注意一点:这个静态函数不能使用类里面的东西

    1. class Student
    2. {
    3. static void test()
    4. {
    5. cout<<"i want to sleep"<
    6. }
    7. };
    8. int main()
    9. {
    10. Student s;
    11. Student::test();//这里记住不能通过类名调用非静态成员函数
    12. s.test();
    13. //上面两句说明类的对象可以使用静态和非静态成员函数
    14. return 0;
    15. }

    这两种方法都可以调用这个静态函数

    3.2 类里面的静态成员变量

    静态成员变量在类的所有实例之间共享,并且可以在不创建类的情况下访问(在类创建之前就创建好了

    3.3静态成员变量的特点

    共享性

    静态成员变量在类所有的实例之间共享,这个类的每个对象的静态变量都是相同的

    存储位置

    静态成员变量存储在静态存储区,而不是每个对象的堆栈中。这使得他们可以在不创建类的实例的情况下访问

    生命周期

    与程序的生命周期一样。在程序启动的时候就创建了,在程序结束的时候就销毁

    访问权限

    静态成员变量可以通过类名来访问,也可以通过对象来访问。但是建议前者,这样子能强调他们的共享性

    如何初始化?

    1. class Student
    2. {
    3. private:
    4. static int a;
    5. };
    6. int Student::a=0;

     4构造函数

    构造函数的主要作用不是开辟空间创建对象,而是为了初始化对象

    特征

    他不是开空间创建对象,而是初始化对象

    1. 无返回值(也不用写void)

    2. 函数名与类名相同

    3. 构造函数可以重载

    1. #include
    2. using namespace std;
    3. class Student
    4. {
    5. public:
    6. Student(int a,int b,int c)//构造函数
    7. {
    8. _a=a;
    9. _b=b;
    10. _c=c;
    11. }
    12. Student(int a,int b)
    13. {
    14. _a=a;
    15. _b=b;
    16. }
    17. void Show()
    18. {
    19. printf("%d %d %d",_a,_b,_c);
    20. }
    21. private:
    22. int _a,_b,_c;
    23. };
    24. int main()
    25. {
    26. Student s(1,2,3);//创建s对象的同时调用构造函数
    27. Student p(1,2);//函数重载
    28. s.Show();
    29. return 0;
    30. }
    31. //输出结果1 2 3
    1. #include
    2. using namespace std;
    3. class Student
    4. {
    5. public:
    6. void Show()
    7. {
    8. printf("%d %d %d",_a,_b,_c);
    9. }
    10. private:
    11. int _a,_b,_c;
    12. };
    13. int main()
    14. {
    15. Student s;
    16. s.Show();
    17. return 0;
    18. }
    19. /*
    20. 输出结果32767 0 0
    21. 如果类里面没有构造函数,则C++编译器会自动调用一个无参的默认构造函数
    22. */
    1. private:
    2. int _a=1,_b=1,_c=1;
    3. /*
    4. 这里对_a,_b,_c不是赋值不是初始化,而是给缺省值
    5. 原因:这里的变量都是声明,还没有创建出来
    6. */

    5析构函数

    作用与构造函数相反。在对象销毁时自动调用,完成对象中资源的清理工作

    特征

    析构函数名是在类名的签名加~

    无参数无返回值

    一个类只能有一个析构函数,如果自己没有定义,系统会调用默认的析构函数。析构函数不能重载

    对象生命周期结束时调用

    析构函数的执行在return之前

    1. #include
    2. using namespace std;
    3. class S
    4. {
    5. public:
    6. S()
    7. {
    8. cout << "构造" << endl;
    9. }
    10. ~S()
    11. {
    12. cout << "析构" << endl;
    13. }
    14. };
    15. int main()
    16. {
    17. S s;
    18. return 0;
    19. }

    先输出构造,然后再输出析构

     作用

    当我们在类中声明了一些指针变量的时候,我们一般在析构函数里面释放这些指针变量所占有的空间,因为系统不会释放指针变量指向的空间,我们需要自己来delete

    6.构造函数和析构函数的一些应用

    6.1 定义为局部变量

    先补充一个知识点:局部变量存储在栈区,全局变量和静态变量存储在静态区

    1. #include
    2. using namespace std;
    3. class Date
    4. {
    5. public:
    6. Date(int a)
    7. {
    8. _a = a;
    9. cout << "Date()->" << _a << endl;
    10. }
    11. ~Date()
    12. {
    13. cout << "~Date()->" << _a << endl;
    14. }
    15. private:
    16. int _a;
    17. };
    18. int main()
    19. {
    20. Date a(1);
    21. Date b(2);
    22. return 0;
    23. }

    输出结果

    析构函数的时候为什么先输出~Date()->2呢?

    首先我们要知道a和b变量都是局部变量,都存储在栈区,栈区遵循后进先出的原则

     6.2 局部变量和全局变量同时存在时

    1. #include
    2. using namespace std;
    3. class Date
    4. {
    5. public:
    6. Date(int a)
    7. {
    8. _a = a;
    9. cout << "Date()->" << _a << endl;
    10. }
    11. ~Date()
    12. {
    13. cout << "~Date()->" << _a << endl;
    14. }
    15. private:
    16. int _a;
    17. };
    18. Date b(2);
    19. int main()
    20. {
    21. Date a(1);
    22. return 0;
    23. }

    输出结果

    全局变量先定义,在程序结束之后再销毁(在局部变量销毁之后再销毁)

     6.3 静态变量、局部变量、全局变量共存

    1. #include
    2. using namespace std;
    3. class Date
    4. {
    5. public:
    6. Date(int a)
    7. {
    8. _a = a;
    9. cout << "Date()->" << _a << endl;
    10. }
    11. ~Date()
    12. {
    13. cout << "~Date()->" << _a << endl;
    14. }
    15. private:
    16. int _a;
    17. };
    18. Date b(2);
    19. int main()
    20. {
    21. Date a(1);
    22. static Date c(3);
    23. return 0;
    24. }

    输出结果

    创建变量按顺序来。首先是局部变量被销毁,静态区的变量(只考虑这个区域时)在销毁时也是类似“后进先出”一样来销毁

     6.4 静态变量在循环中

    1. #include
    2. using namespace std;
    3. class Date
    4. {
    5. public:
    6. Date(int a)
    7. {
    8. _a = a;
    9. cout << "Date()->" << _a << endl;
    10. }
    11. ~Date()
    12. {
    13. cout << "~Date()->" << _a << endl;
    14. }
    15. private:
    16. int _a;
    17. };
    18. int main()
    19. {
    20. for (int i = 0; i <= 1; i++)
    21. {
    22. static Date a(1);
    23. Date b(2);
    24. }
    25. return 0;
    26. }

    输出结果

    静态变量只会被创建一次,所以只有一次构造函数和一次析构函数

     7.拷贝构造函数

    浅拷贝

    拷贝成员变量的值

    1. #include
    2. using namespace std;
    3. class Student
    4. {
    5. public:
    6. Student(int a, int b, int c)
    7. {
    8. _a = a;
    9. _b = b;
    10. _c = c;
    11. }
    12. /*
    13. 当有Student b(a)这行代码的时候
    14. 问题1:为什么要写成 Student &s呢?
    15. 写成Student &s表示s是a的别名,不需要调用拷贝构造函数
    16. 如果写成Student s的话,会无限递归
    17. 原因:在调用拷贝构造函数的时候,
    18. Student(Student s)
    19. {
    20. this->_a = s._a;
    21. this->_b = s._b;
    22. this->_c = s._c;
    23. }
    24. 把a传给s也会调用拷贝够咱函数,也就是说,调用一个拷贝构造函数会使得其内部嵌套另一个拷贝构造函数
    25. 那这个嵌套的拷贝构造函数也会继续嵌套一个,依次无限嵌套下去而导致死循环;所以要加一个&
    26. 问题2:那为什么加一个const呢?
    27. 因为我们要a的值不变,将其内部的值赋给b(s),所以我们最好加一个const来修饰来让a的值不变
    28. 只要a里面的值变,就会报错
    29. 因为有可能出现以下情况
    30. Student(const Student &s)
    31. {
    32. a._a = this->_a;
    33. a._b = this->_b;
    34. a._c = this->_c;
    35. }
    36. 如果没有const,出现上述情况编译器是不会报错的
    37. */
    38. Student(const Student &s)
    39. {
    40. this->_a = s._a;
    41. this->_b = s._b;
    42. this->_c = s._c;
    43. }
    44. void Show()
    45. {
    46. printf("%d %d %d\n", _a, _b, _c);
    47. }
    48. private:
    49. int _a, _b, _c;
    50. };
    51. int main()
    52. {
    53. Student a(10, 20, 30);
    54. Student b(a);
    55. b.Show();
    56. return 0;
    57. }

    输出结果

     8.重载

    关键字:operator

    8.1 运算符重载

    1. #include
    2. using namespace std;
    3. class Student
    4. {
    5. public:
    6. Student(int a, int b, int c)
    7. {
    8. _a = a;
    9. _b = b;
    10. _c = c;
    11. }
    12. bool operator==(const Student &b)//重载==运算符
    13. {
    14. return _a == b._a && _b == b._b && _c == b._c;//a和b相等的条件
    15. }
    16. private:
    17. int _a, _b, _c;
    18. };
    19. int main()
    20. {
    21. Student a(10, 20, 30);
    22. Student b(10, 20, 30);
    23. cout << (a == b) << endl;//这里判断a和b是否相等只需要判断a和b里面的_a,_b,_c是否分别相等
    24. return 0;
    25. }
    1. #include
    2. using namespace std;
    3. class Student
    4. {
    5. public:
    6. Student(int a, int b, int c)
    7. {
    8. _a = a;
    9. _b = b;
    10. _c = c;
    11. }
    12. int operator-(const Student &b)
    13. {
    14. return b._c - _c;
    15. }
    16. private:
    17. int _a, _b, _c;
    18. };
    19. int main()
    20. {
    21. Student a(10, 20, 30);
    22. Student b(10, 20, 50);
    23. cout << (b - a) << endl;
    24. return 0;
    25. }
    26. //输出结果是a._c - b._c 结果为-20

    这里就有一个问题

    void operator++是怎么区别前置++和后置++的呢?

    1. int operator++();//前置++
    2. int operator++(int);//后置++
    3. //规定:()里面无参数的为前置++,否则为后置++

    注意点

    1. #include
    2. using namespace std;
    3. class A
    4. {
    5. public:
    6. A(int val){
    7. _val=val;
    8. }
    9. void operator+(const A& d){
    10. _val+=d._val;
    11. }
    12. void operator=(const A& d){
    13. _val=d._val;
    14. }
    15. void Print(){
    16. cout<<_val<
    17. }
    18. private:
    19. int _val;
    20. };
    21. int main()
    22. {
    23. A a(3),b(4),c(7);
    24. c=a=b;
    25. /*
    26. c=a=b会报错,但是a=b,不会报错
    27. 因为c=a=b这句话首先执行的是a=b,然后返回值是void
    28. 所以
    29. void operator=(const A& d){
    30. _val=d._val;
    31. }
    32. 应该改成
    33. A operator=(const A& d){
    34. _val=d._val;
    35. return *this;//这里返回值是*this的一个拷贝(拷贝的话可能调用其中的拷贝函数或者构造函数什么的)
    36. }
    37. 所以最好写成
    38. A& operator=(const A& d){
    39. _val=d._val;
    40. return *this;//这里返回的是*this的别名,不会再去调用拷贝函数
    41. }
    42. */
    43. a.Print();
    44. return 0;
    45. }

    8.2 流重载

    1. #include
    2. using namespace std;
    3. class Date
    4. {
    5. public:
    6. Date(int year, int month, int day)
    7. {
    8. _year = year;
    9. _month = month;
    10. _day = day;
    11. }
    12. void operator<<(ostream &out) // out是cout的别名
    13. {
    14. out << _year << '/' << _month << '/' << _day << endl;
    15. }
    16. void operator>>(istream &in)
    17. {
    18. in>>_year>>_month>>_day;
    19. }
    20. private:
    21. int _year, _month, _day;
    22. };
    23. int main()
    24. {
    25. Date a(2023, 9, 16);
    26. a>>cin;//a是被操作的对象,cin传给in
    27. a<
    28. return 0;
    29. }

    const的参与

    1. #include
    2. using namespace std;
    3. class Date
    4. {
    5. public:
    6. Date(int year, int month, int day)
    7. {
    8. _year = year;
    9. _month = month;
    10. _day = day;
    11. }
    12. void Print()
    13. {
    14. printf("%d %d %d\n", _year, _month, _day);
    15. }
    16. private:
    17. int _year, _month, _day;
    18. };
    19. int main()
    20. {
    21. Date a(2023, 9, 17);
    22. const Date b(1,1,1);
    23. a.Print();
    24. b.Print();//这里会报错
    25. /*
    26. 因为b是一个const Date类型的
    27. b.Print()的原型是b.Print(&b),&b是一个const Date*类型的
    28. void Print()的原型是void Print(Date* const this)
    29. &b从const Date*变成Date*权限放大了
    30. 所以我们将void Print()写成void Print() const,这样他的原型变成了:
    31. void Print(const Date* const this)是const Date*类型,后面那个const是修饰this
    32. 让this一直指向这个类且不能变
    33. */
    34. return 0;
    35. }
    1. #include
    2. using namespace std;
    3. class Date
    4. {
    5. public:
    6. Date(int year, int month, int day)
    7. {
    8. _year = year;
    9. _month = month;
    10. _day = day;
    11. }
    12. void Print()const
    13. {
    14. printf("%d %d %d\n", _year, _month, _day);
    15. }
    16. bool operator<(const Date& d)
    17. {
    18. return _year
    19. }
    20. private:
    21. int _year, _month, _day;
    22. };
    23. int main()
    24. {
    25. Date a(2023, 9, 17);
    26. const Date b(1,1,1);
    27. a//不会报错
    28. b//这里会报错
    29. /*
    30. bool operator<(const Date& d)的原型是
    31. bool operator
    32. 所以写成b
    33. b由const Date*变成Date*,权限变大
    34. 可以写成
    35. bool operator<(const Date& d)const
    36. */
    37. return 0;
    38. }

     9.初始化列表

    初始化列表可以认为是成员变量定义的地方

    (private里面是声明)

    9.1格式

    1. #include
    2. using namespace std;
    3. class Date
    4. {
    5. public:
    6. //格式如下
    7. Date(int a)
    8. : _a(a),
    9. _b(a)
    10. {
    11. }
    12. void Print()
    13. {
    14. cout << _a << endl;
    15. }
    16. private:
    17. int _a;
    18. int &_b;
    19. };
    20. int main()
    21. {
    22. Date a(6);
    23. a.Print();
    24. return 0;
    25. }

    9.2例子

    每个成员变量在初始化列表中只能出现一次

    类中包含以下成员,必须放在初始化列表位置进行初始化

    1.引用成员变量

    2.const成员变量

    3.自定义类型成员

    1. class A
    2. {
    3. public:
    4. A(int a)
    5. : _a(a)
    6. {
    7. }
    8. void Print()
    9. {
    10. cout << _a << endl;
    11. }
    12. private:
    13. int _a;
    14. };
    15. class B
    16. {
    17. public:
    18. B(int a, int b)
    19. : _b(b),
    20. _a(a),
    21. _n(10),
    22. _r(b)
    23. {
    24. }
    25. void Print()
    26. {
    27. _a.Print();
    28. cout << _b << endl;
    29. }
    30. private:
    31. int _b;
    32. A _a;
    33. const int _n;
    34. int& _r;
    35. };

     成员变量在类中声明的次序就是其在初始化列表里面初始化的次序,与其在初始话列表里面的先后顺序无关

    例子:

    1. #include
    2. using namespace std;
    3. class A
    4. {
    5. public:
    6. A(int a)
    7. :_a(a),
    8. _b(_a)
    9. {}
    10. void Print()
    11. {
    12. cout<<_a<
    13. cout<<_b<
    14. }
    15. private:
    16. int _b;
    17. int _a;
    18. };
    19. int main()
    20. {
    21. A a(3);
    22. a.Print();
    23. return 0;
    24. }
    25. /*
    26. 输出结果是 3 随机值
    27. 因为先是把_a赋值给_b,再把a赋值给_a
    28. */

    9.3explicit关键字

    用explicit修饰的构造函数,会禁止单参构造函数的隐式转换

    首先搞明白什么是隐式转换

    1. class A
    2. {
    3. public:
    4. A(int a)//单参构造函数
    5. : _a(a)
    6. {
    7. }
    8. private:
    9. int _a;
    10. };
    11. //定义一个类对象
    12. A a(10);
    13. /*
    14. 此时_a的值为10,如果进行以下操作,a=20,这样子会把_a的值变为20
    15. 编译器会用20(将20赋值给无名对象的_a)构造一个无名对象
    16. 最后用无名对象赋值给对象a,这种转换就叫隐式转换
    17. */

    隐式转换其实还有很多,比如

    int a=2;

    double b=a;

    编译器会先引入一个中间变量,类型为double,然后再将这个值赋值给b

    1. #include
    2. using namespace std;
    3. class A
    4. {
    5. public:
    6. A(int a)
    7. /*
    8. 如果这里改成explicit A(int a)的话
    9. a=5那行代码会报错
    10. 因为explicit会禁止单参构造函数的隐式转换
    11. */
    12. : _a(a)
    13. {
    14. }
    15. void Print()
    16. {
    17. cout << _a << endl;
    18. }
    19. private:
    20. int _a;
    21. };
    22. int main()
    23. {
    24. A a(3);
    25. a = 5;
    26. a.Print();
    27. return 0;
    28. }

     9.4 匿名对象

    生命周期只有它所在的那一行

    1. class A
    2. {
    3. public:
    4. A(int a)
    5. :_a=a
    6. {}
    7. private:
    8. int _a;
    9. };
    10. A a(10);
    11. a = A(20);
    12. a.Print();
    13. 如果不使用匿名对象,那就需要:
    14. A a(10);
    15. A b(20);
    16. a=b;
    17. 这样子来操作
    18. /*
    19. A(20)表示创建一个匿名对象,他的_a值为20
    20. 这个匿名对象创建的时候也会调用构造函数和析构函数
    21. */

    10.友元类

    如果类A是类B的友元类,那么B可以调用A里面的private成员和protected成员

    但是这样子做会破坏类的封装性

    注意点

    1. 友元关系不能被继承

    2. 友元关系是单向的,A是B的友元,但是B不一定是A的友元

    3. 友元不具有传递性,比如A是B的友元,B是C的友元,但是A不是C的友元

    1. #include
    2. using namespace std;
    3. class A
    4. {
    5. public:
    6. friend class B;//代表A是B的友元类,B可以访问A里面的所有成员
    7. A(int a)
    8. {
    9. _a=a;
    10. }
    11. private:
    12. int _a;
    13. };
    14. class B
    15. {
    16. public:
    17. B(int b)
    18. {
    19. _b=b;
    20. }
    21. void Show_A_a(A a)
    22. {
    23. cout<//这里就体现了可以访问A定义的对象a里面的私有成员
    24. }
    25. private:
    26. int _b;
    27. };
    28. int main()
    29. {
    30. A a(666);
    31. B b(555);
    32. b.Show_A_a(a);
    33. return 0;
    34. }

    10. 2内部类

    定义:如果一个类定义在另一个类的里面,那这个里面的类就叫做内部类

    内部类是外部内的友元类,但是外部类不是内部类的友元类

    比如A是外部类,B是外部类

    那么定义一个类的时候应该A::B b;定义一个对象b,b能调用A和B里面的成员,但是如果A a,那a不能调用B里面的成员

  • 相关阅读:
    算法竞赛备赛进阶之状态机模型训练
    [java刷算法]牛客—剑指offer链表有环的入口、反转链表、合并排序链表
    JavaEE进阶(5/27)Spring Boot
    Scapy样例三则
    安卓毕业设计选题基于Uniapp实现的Android的餐饮管理系统实现的App
    Windows上安装和配置Apache Kafka
    浅谈分布式系统
    2023年首届天府杯数学建模国际大赛问题A思路详解与参考代码:大地测量数据中异常现象的特征和识别
    UI设计工具都哪些常用的,推荐这5款
    MySQL事务——事务隔离界别,MVCC
  • 原文地址:https://blog.csdn.net/pengyupan/article/details/132643287