【C++】类和对象(三)

[复制链接]
发表于 2025-12-13 19:31:06 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×

前言

本日小编来给类和对象结个尾。本章节呢偏重把握初始化列表,其他的呢我们可以相识一下下的。那接下来就来一起来看看吧。


一、初始化列表

之前我们实现构造函数时,初始化成员变量重要使⽤函数体内赋值,构造函数初始化尚有⼀种⽅式,就是初始化列表,初始化列表的使⽤⽅式是以⼀个冒号开始,接着是⼀个以逗号分隔的数据成员列表,每个"成员变量"后⾯跟⼀个放在括号中的初始值或表达式。
比如:
  1. #include<iostream>
  2. #include<stdbool.h>
  3. using namespace std;
  4. class Date
  5. {
  6. public:
  7.         Date(int year=2024, int month=12, int day=1)
  8.                 :_year(year)//初始化列表
  9.                 ,_month(month)
  10.                 ,_day(day)
  11.         {}//括号里面是构造函数的内部
  12.         void Print()
  13.         {
  14.                 cout << _year << "/" << _month << "/" << _day << endl;
  15.         }
  16. private:
  17.         int _year;
  18.         int _month;
  19.         int _day;
  20. };
  21. int main()
  22. {
  23.         Date d1(2024,12,1);
  24.         d1.Print();
  25.         return 0;
  26. }
复制代码
这里的{}内里着实就是构造函数的内容。由于构造函数就是给成员变量初始化的,但是这里呢初始化列表就已经完成了成员变量的初始化了,以是构造函数内里的内容就没有须要写了。它完备的是如许的。
  1. public:
  2.         Date(int year = 2024, int month = 12, int day = 1)
  3.                 :_year(year)//初始化列表
  4.                 , _month(month)
  5.                 , _day(day)
  6.         {
  7.             _year = year;
  8.                 _month = month;
  9.                 _day = day;;
  10.         }
复制代码
但是这两者是不是一样的意义呢?以是我们上面就省略了构造函数内里的内容,由于初始化列表已经初始化好了。我们再来看当作员变量背面括号内里时表达式的。
  1. using namespace std;
  2. class Stack
  3. {
  4. public:
  5.         Stack(int capacity=4)
  6.                 :_capacity(capacity)//初始化列表
  7.                 , _top(0)
  8.                 ,_arr((int*)malloc(sizeof(int) * capacity))//开辟空间
  9.         {
  10.                 //构造函数内部
  11.                 if (_arr == nullptr)
  12.                 {
  13.                         perror("malloc fail");
  14.                         exit(1);
  15.                 }
  16.         }
  17.         void STPush(const int& data)//入栈
  18.         {
  19.                 //判断是否要扩容
  20.                 _arr[_top++] = data;
  21.         }
  22.         ~Stack()
  23.         {
  24.                 if (_arr)
  25.                 {
  26.                         free(_arr);
  27.                         _arr = nullptr;
  28.                         _capacity = _top = 0;
  29.                 }
  30.         }
  31. private:
  32.         int* _arr;
  33.         int _capacity;
  34.         int _top;
  35. };
复制代码
像上面的_arr背面就是一个表达式的。既然初始化列表和默认构造函数都是初始化的,那我们可不可以把两者混淆着用呢?固然可以的。就像如许:
  1. class Stack
  2. {
  3. public:
  4.         Stack(int capacity=4)
  5.                 :_capacity(capacity)//初始化列表
  6.                 , _top(0)
  7.         {
  8.                 //构造函数内部
  9.                 _arr=(int*)malloc(sizeof(int) * capacity);
  10.                 if (_arr == nullptr)
  11.                 {
  12.                         perror("malloc fail");
  13.                         exit(1);
  14.                 }
  15.         }
复制代码
像如许就是先用初始化列表先初始化_capacity和_top,然后在用构造函数来初始化_arr。
注意:从语法上来说初始化列表可以明白成每个成员变量界说初始化的地方。以是每个成员变量在初始化列表中只能出现⼀次

2.初始化列表最告急的作用就是为特别的成员变量提供初始化资助:
1.引用变量
2.const成员变量
3.自界说范例成员(且该类没有默认构造函数时)

初始化列表就是界说初始化的地方,当我们界说一个引用变量时,是不是要得有引用对象呀?否则我们怎么知道我们时给谁取得别名呢?是不是呀?

以是这里必须要给一个要引用得对象。
当我们在界说一个const变量时。通常须要在界说得时间就要初始化,否则步伐会报错得哦

当我们自界说了一个范例成员(且该类没有默认构造函数时)那就必须在初始化列表位置初始化。如果不初始化步伐会报错得。就像如许
  1. #include<iostream>
  2. #include<stdbool.h>
  3. using namespace std;
  4. class A
  5. {
  6. public:
  7.         A(int al)
  8.                 :_al(al)
  9.         {}
  10. private:
  11.         int _al;
  12. };
  13. class Date
  14. {
  15. public:
  16.         Date(int year = 2024, int month = 12, int day = 1)
  17.                 :_year(year)//初始化列表
  18.                 , _month(month)
  19.                 , _day(day)
  20.         {}
  21.         void Print()
  22.         {
  23.                 cout << _year << "/" << _month << "/" << _day << endl;
  24.         }
  25. private:
  26.         int _year;
  27.         int _month;
  28.         int _day;
  29.         A _t;
  30. };
复制代码

总结:引⽤成员变量,const成员变量,没有默认构造的类范例变量,必须放在初始化列表位置进⾏初始化,否则会编译报错。
3.C++11⽀持在成员变量声明的位置给缺省值,这个缺省值重要是给没有显⽰在初始化列表初始化的成员使⽤的。
  1. #include<iostream>
  2. #include<stdbool.h>
  3. using namespace std;
  4. class Date
  5. {
  6. public:
  7.         Date(int year = 2024, int month = 12, int day = 1)
  8.                 :_year(year)//初始化列表
  9.                 , _month(month)
  10.                 //,_day(day)
  11.         {}
  12.         void Print()
  13.         {
  14.                 cout << _year << "/" << _month << "/" << _day << endl;
  15.         }
  16. private:
  17.    //成员声明
  18.         int _year=2024;//这里需要注意的是:虽然这里看着像是在定义初始化,但是这里是声明哦!
  19.         int _month=12;
  20.         int _day = 3;
  21. };
  22. int main()
  23. {
  24.         Date d1(2024, 12, 1);
  25.         d1.Print();
  26.         return 0;
  27. }
复制代码

4.初始化列表中按照成员变量在类中声明次序进⾏初始化,跟成员在初始化列表出现的的先后次序⽆关。发起声明次序和初始化列表次序保持⼀致
  1. #include<iostream>
  2. #include<stdbool.h>
  3. using namespace std;
  4. class A
  5. {
  6. public:
  7.         A(int a)
  8.             :_b(a)
  9.                 ,_a(_b)
  10.         {}
  11.         void Print()
  12.         {
  13.                 cout << _a << "   " << _b << endl;
  14.         }
  15. private:
  16.         int _a = 1;
  17.         int _b = 1;
  18. };
  19. int main()
  20. {
  21.         A d(2);
  22.         d.Print();
  23.         return 0;
  24. }
复制代码
这段代码呢如果我们按正常的逻辑来走的话,那么它输出就是一个2 2 。但是初始化的次序是按成员变量的次序来的。以是这里是先初始化_a,但是我们给他初始化的是_b,_b是int(内置范例),调用_b的构造函数。由于构造函数对内置范例不处理处罚。以是这里大概是0,也大概是随机值。这个要看编译器。以是这里输出的是一个随机值和2。

1.初始化列表的总结

1.无论是否体现写初始化列表,每个构造函数都有初始化列表
2.无论是否在初始化列表体现写初始化。每个成员变量都要走初始化列表

二、static成员

1.⽤static修饰的成员变量,称之为静态成员变量,静态成员变量⼀定要在类外进⾏初始化。这里 静态成员变量不能在声明位置给缺省值初始化,由于缺省值是个构造函数初始化列表的,静态成员变量不属于某个对象,不⾛构造函数初始化列表。


2.静态成员变量为全部类对象所共享,不属于某个详细的对象,不存在对象中,存放在静态区。
3.⽤static修饰的成员函数,称之为静态成员函数,静态成员函数没有this指针。以是静态成员函数可以访问其他静态成员了,但是不能访问非静态成员。
  1. #include<iostream>
  2. using namespace std;
  3. class A
  4. {
  5. public:
  6.         A()
  7.                 :_a(1)
  8.         {}
  9.         static int Geta()
  10.         {
  11.                 _a++;//这里是访问不到的。因为我们访问非静态成员的时候是通过this指针访问的
  12.                 ++a;
  13.                 return a;
  14.         }
  15.         void Print()
  16.         {
  17.                 cout << Geta() << "   " << _a << endl;
  18.         }
  19. private:
  20.         int _a;
  21.         static int a;
  22. };
  23. int A::a = 0;
  24. int main()
  25. {
  26.         A b;
  27.         b.Print();
  28.         return 0;
  29. }
复制代码
3.固然静态函数不能访问非静态的成员。但好坏静态员函数可以访问恣意的静态成员变量和静态成员函数。比如:
  1. #include<iostream>
  2. using namespace std;
  3. class A
  4. {
  5. public:
  6.         A()
  7.                 :_a(1)
  8.         {}
  9.         static int Geta()
  10.         {
  11.                 ++a;
  12.                 return a;
  13.         }
  14.          int Get_a() //非静态成员函数
  15.         {
  16.                 _a++;//非静态成员变量
  17.                 Geta();//静态成员函数
  18.                 return _a;
  19.         }
  20.         void Print()
  21.         {
  22.                 cout << Geta() << "  " << Get_a() << endl;
  23.         }
  24. private:
  25.         int _a;
  26.         static int a;
  27. };
  28. int A::a = 0;
  29. int main()
  30. {
  31.         A b;
  32.         b.Print();
  33.         return 0;
  34. }
复制代码
注意:
1.静态成员也是类的成员,受publicprotected、private 访问限定符的限定, 只要突破类域就可以通过类名::静态成员大概对象.静态成员来访问静态成员大概成员函数。
1.实践

标题:求1+2+3+…+n,
要求:不能使用乘除法,for,while,if,else,switch,case等关键字及条件判定语句。
数据范围:0<=n<=200;
进阶:空间复杂度:O(1),时间复杂度:O(n)
办理思绪:这里的难点就怎样在倒霉用循环的环境下调用n次。着实这里就可以联合之前小编之前分享的构造函数那一块我们直接通过调用构造函数来实现这里的调用n次。办理了n次调用,那接下来实现全局的一个累加就行了,但是C++呢迁就封装,以是这里要用到静态成员变量。代码如下
  1. #include<iostream>
  2. using namespace std;
  3. class sum
  4. {
  5. public:
  6.         sum()
  7.         {
  8.                 _ret += _i;
  9.                 ++_i;
  10.         }
  11.          static  int Get_ret()
  12.         {
  13.                 return _ret;
  14.         }
  15. private:
  16.         static  int _i;
  17.         static  int _ret;
  18. };
  19. int sum::_i = 1;
  20. int sum::_ret = 0;
  21. class Solution
  22. {
  23. public:
  24.         int sum_Solution(int n)
  25.         {
  26.                 sum *p1 = new sum[n];//申请n个空间,调用n次sum的构造函数(new小编在内存管理那块会讲的)
  27.                 //sum[n];//c99变长数组,vs不支持
  28.                 return sum::Get_ret();
  29.         }
  30. private:
  31. };
  32. int main()
  33. {
  34.         Solution d;
  35.         cout << d.sum_Solution(5) << endl;
  36. }
复制代码

三、友元

友元提供了⼀种突破类访问限定符封装的⽅式,友元分为:友元函数和友元类,在函数声明大概类声明的前⾯加friend,而且把友元声明放到⼀个类的⾥⾯
  1. #include<iostream>
  2. using namespace std;
  3. class A
  4. {
  5.         //友元声明
  6.         friend void fun(const A&d);
  7. public:
  8. private:
  9.         int _a = 1;
  10.         int _b = 2;
  11. };
  12. void fun(const A& d)//友元函数
  13. {
  14.         cout << d._a <<d._b  <<endl;
  15. }
  16. int main()
  17. {
  18.         A d;
  19.         fun(d);
  20.         return 0;
  21. }
复制代码
友元的特点:
1.⼀个函数可以是多个类的友元函数。 比方:
  1. #include<iostream>
  2. using namespace std;
  3. class B;//这里要前置声明,不然A的友元函数声明编译器不知道B是什么。
  4. class A
  5. {
  6.         //友元声明
  7.         friend void fun(const A& d, const B& b);
  8. public:
  9. private:
  10.         int _a = 1;
  11.         int _b = 2;
  12. };
  13. class B
  14. {
  15.         friend void fun(const A& d, const B& b);
  16. public:
  17. private:
  18.         int _aa = 2;
  19.         int _bb = 2;
  20. };
  21. void fun(const A& d,const B&b)//友元函数
  22. {
  23.         cout << d._a <<d._b  <<endl;
  24.         cout << b._aa << b._bb << endl;
  25.        
  26. }
  27. int main()
  28. {
  29.         A aa;
  30.         B bb;
  31.         fun(aa,bb);
  32.         return 0;
  33. }
复制代码
2.友元类中的成员函数都可以是另⼀个类的友元函数,都可以访问另⼀个类中的私有和掩护成员。
3.友元类的关系是单向的,不具有互换性,⽐如A类是B类的友元,但是B类不是A类的友元。
4.友元类关系不能转达,如果A是B的友元, B是C的友元,但是A不是C的友元。
注意:
1.外部友元函数可访问类的私有和掩护成员,友元函数仅仅是⼀种声明,他不是类的成员函数。而且友元函数可以在类界说的任何地⽅声明,不受类访问限定符限定。
2.偶尔提供了便利。但是友元会增长耦合度,粉碎了封装,以是友元不宜多⽤。

四、内部类

如果⼀个类界说在另⼀个类的内部,这个内部类就叫做内部类。 内部类是⼀个独⽴的类,跟界说在全局相⽐,他只是受外部类类域限定和访问限定符限定,以是外部类界说的对象中不包罗内部类。
内部类默认是外部类的友元类。
  1. #include<iostream>
  2. using namespace std;
  3. class A
  4. {
  5. public:
  6.         class B//B默认是A的友元
  7.         {
  8.         public:
  9.                 void fun(const A&d)
  10.                 {
  11.                         cout << d._a << d._b << endl;
  12.                 }
  13.         private:
  14.         };
  15. private:
  16.         int _a=1;
  17.         int _b=2;
  18. };
  19. int main()
  20. {
  21.         A::B b;
  22.         A a;
  23.         b.fun(a);
  24.         return 0;
  25. }
复制代码
内部类本质也是⼀种封装,当A类跟B类精密关联,A类实现出来重要就是给B类使⽤,那么可以思量把A类计划为B的内部类,如果放到private/protected位置,那么A类就是B类的专属内部类,其他地⽅都⽤不了。
比如我们上面的谁人训练题一样。我们就可以把sum设置成内部类:
  1. #include<iostream>
  2. using namespace std;
  3. class Solution
  4. {
  5.         class sum//sum是Solution的友元
  6.         {
  7.         public:
  8.                 sum()
  9.                 {
  10.                         _ret += _i;
  11.                         ++_i;
  12.                 }
  13.                 static  int Get_ret()
  14.                 {
  15.                         return _ret;
  16.                 }
  17.         private:
  18.         };
  19.         static  int _i;
  20.         static  int _ret;
  21. public:
  22.         int sum_Solution(int n)
  23.         {
  24.                 sum* p1 = new sum[n];//申请n个空间,调用n次sum的构造函数(new小编在内存管理那块会讲的)
  25.                 //sum[n];//c99变长数组,vs不支持
  26.                 return sum::Get_ret();
  27.         }
  28. private:
  29. };
  30. int Solution::_i = 1;
  31. int Solution::_ret = 0;
  32. int main()
  33. {
  34.         Solution d;
  35.         cout << d.sum_Solution(5) << endl;
  36. }
复制代码
五、匿名对象

⽤范例(实参) 界说出来的对象叫做匿名对象,相⽐之前我们界说的 范例 对象名(实参) 界说出来的叫著名对象
注意:匿名对象⽣命周期只在当前⼀⾏,⼀般临时界说⼀个对象当前⽤⼀下即可,就可以界说匿名对象。
  1. #include<iostream>
  2. using namespace std;
  3. class A
  4. {
  5. public:
  6.         void Print()
  7.         {
  8.                 cout << _a << "  " << _b << endl;
  9.         }
  10.         ~A()
  11.         {
  12.                 cout << "~A()" << endl;
  13.         }
  14. private:
  15.         int _a = 1;
  16.         int _b = 2;
  17. };
  18. int main()
  19. {
  20.         A s;//有名对象
  21.         s.Print();//有名对象的调用
  22.         A();//匿名对象
  23.         A().Print();//匿名对象的调用
  24.         return 0;
  25. }
复制代码

从运行的结果我们可以看到 。匿名对象的生命周期就在当前一行。到了下一行的时间就调用析构函数。
六、范例转换


  • C++⽀持内置范例隐式范例转换为类范例对象,须要有相干内置范例为参数的构造函数。
  1. #include<iostream>
  2. using namespace std;
  3. class A
  4. {
  5. public:
  6.         A(int n = 10)//相关内置类型为参数的构造函数,如果这里没有。程序会报错的。
  7.                 :_a(n)
  8.         {}
  9.         A(const A& aa)
  10.         {
  11.                 _a = aa._a;
  12.         }
  13.         void Print()
  14.         {
  15.                 cout << _a << "  " << _b << endl;
  16.         }
  17.         ~A()
  18.         {
  19.                 cout << "~A()" << endl;
  20.         }
  21. private:
  22.         int _a = 1;
  23.         int _b = 3;
  24. };
  25. int main()
  26. {
  27.         A d(1);
  28.         d.Print();
  29.         A d1=2;//这里是2先构造一个A的临时对象,然后临时对象拷贝构造d1.编译器在连续构造+拷贝构造的时候
  30.                //编译器会直接优化成构造
  31.         return 0;
  32. }
复制代码
2.如果我们不想这里隐形范例转换,那么就可以在构造函数前⾯加explicit。如许就不再⽀持隐式范例转换。

像一如许的话就会报错的。不允许隐式范例转化了。
3.类范例的对象之间也可以隐式转换,须要相应的构造函数⽀持
  1. #include<iostream>
  2. using namespace std;
  3. class A
  4. {
  5. public:
  6.          A(int n = 10)//相关内置类型为参数的构造函数。
  7.                 :_a(n)
  8.         {}
  9.         A(const A& aa)
  10.         {
  11.                 _a = aa._a;
  12.         }
  13.         void Print()
  14.         {
  15.                 cout << _a << "  " << _b << endl;
  16.         }
  17.         int get()const
  18.         {
  19.                 return _a + _b;
  20.         }
  21.         ~A()
  22.         {
  23.                 cout << "~A()" << endl;
  24.         }
  25. private:
  26.         int _a = 1;
  27.         int _b = 3;
  28. };
  29. class B
  30. {
  31. public:
  32.         B(const A&d)//相应的构造函数
  33.         {
  34.                 _a = d.get();
  35.     }
  36. private:
  37.         int _a;
  38. };
  39. int main()
  40. {
  41.         A arr(2);
  42.         B b = arr; //类的隐式转换
  43.         return 0;
  44. }
复制代码
4.运用
这里用在栈内里存一个A范例来举例:
  1. class A
  2. {
  3. public:
  4.          A(int n = 10)//相关内置类型为参数的构造函数。
  5.                 :_a(n)
  6.         {}
  7.         A(const A& aa)
  8.         {
  9.                 _a = aa._a;
  10.         }
  11.         void Print()
  12.         {
  13.                 cout << _a << "  " << _b << endl;
  14.         }
  15.         int get()const //
  16.         {
  17.                 return _a + _b;
  18.         }
  19.         ~A()
  20.         {
  21.                 cout << "~A()" << endl;
  22.         }
  23. private:
  24.         int _a = 1;
  25.         int _b = 3;
  26. };
  27. class Stack
  28. {
  29. public:
  30.         void StackPush(const A&d)
  31.         {
  32.                 //......
  33.         }
  34. private:
  35.         A _arr[10];
  36.         int top;
  37. };
  38. int main()
  39. {
  40.         A raa(1);
  41.         A raaa = 2;
  42.         Stack st;
  43.         st.StackPush(raa);//正常传参
  44.         st.StackPush(2);//利用隐式转换传参。相比之下隐式转换要方便得多。
  45.         return 0;
  46. }
复制代码
七、对象拷⻉时的编译器优化(相识)

当代编译器会为了尽大概提⾼步伐的服从,在不影响精确性的环境下会尽大概淘汰⼀些传参和传返回值的过程中可以省略的拷⻉。
怎样优化C++尺度并没有严格规定,各个编译器会根据环境⾃⾏处理处罚。当前主流的相对新⼀点的编译器对于一连⼀个表达式步调中的一连拷⻉会进⾏归并优化,有些更新更"激进"的编译器还会进⾏跨⾏跨表达式的归并优化。
总结

本日就到这里吧。咋们下期再见。有什么题目可以在品评区发表意见的!

回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表