【C++指南】一文总结C++类和对象【下】

[复制链接]
发表于 2025-10-16 17:25:10 | 显示全部楼层 |阅读模式

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

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

×

  🌟 各位看官好,我是egoist2023!
  🌍 种一棵树最好是十年前,其次是如今!
  🚀 本日来学习C++类和对象的语法知识。
  👍 如果以为这篇文章有资助,欢迎您一键三连,分享给更多人哦
  目次
再探构造函数
初始化列表
范例转换
static成员
求1+2+3+...+n 
友元、内部类、匿名对象
友元
内部类
匿名对象
编译器优化


再探构造函数

初始化列表

前面我们讲过构造函数初始化还可以使用初始化列表。那么初始化列表是怎样使用的呢?
   

  • 以⼀个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"背面跟放在括号中的初始值。
  1. Date(int& x, int year = 1, int month = 1, int day = 1)
  2.     :_year(year)
  3.     ,_month(month)
  4.     ,_day(day)
  5. {}
复制代码


  • 每个成员变量在初始化列表中只能出现⼀次,语法明白上初始化列表可以以为是每个成员变量界说初始化的地方。
  • 引用成员变量,const成员变量,无默认构造的类范例变量,必须放在初始化列表位置举行初始化,否则会编译报错。
  1. class Date
  2. {
  3. public:
  4. Date()
  5.     :_ref(x)
  6.     ,_n(1)
  7. {}
  8. private:
  9.     int& _ref; // 引⽤成员变量
  10.     const int _n; // const成员变量
  11. };
复制代码


  • C++11⽀持在成员变量声明的位置给缺省值,这个缺省值重要是给没有显⽰在初始化列表初始化的 成员使⽤的。
  • 只管使用初始化列表初始化,由于那些不在初始化列表初始化的成员也会走初始化列表,如果这个成员在声明位置给了缺省值,初始化列表则用这个缺省值初始化。如果没给缺省值,对于没表如今初始化列表初始化的内置范例成员是否初始化取决于编译器,C++并没有规定。对于没有表如今初始化列表初始化的⾃界说范例成员会调⽤这个成员范例的默认构造函数,如果没有默认构造会编译错误。
   
  1. #include<iostream>
  2. using namespace std;
  3. class A
  4. {
  5. public:
  6.         A(int a)
  7.                 :_a1(a)
  8.                 , _a2(_a1)
  9.         {}
  10.         void Print() {
  11.                 cout << _a1 << " " << _a2 << endl;
  12.         }
  13. private:
  14.         int _a2 = 2;
  15.         int _a1 = 2;
  16. };
  17. int main()
  18. {
  19.         A aa(1);
  20.         aa.Print();
  21. }
复制代码
上面的步伐中运行结果是什么?
  实例化对象时传值为1传参,调用构造函数,通过初始化列表将_a1初始化为1,然后再把_a2初始化为1。调用Print成员函数,打印后发现值为1 和随机值。这是为什么呢?
  

  须要注意初始化列表是跟类中声明次序走,因此是先把_a2初始化为_a1(_a1还没初始化),再将_a1初始化为1。
  
  

  • 初始化列表中按照成员变量在类中声明次序举行初始化,跟成员在初始化列表出现的的先后次序无关。发起声明次序和初始化列表次序保持⼀致。
  范例转换

  1. int main()
  2. {
  3.         int a = 1;
  4.         const double& b = a;
  5.         return 0;
  6. }
复制代码
在上面这段步伐中,界说了int范例的变量a,double范例的b引用a,此时会发生隐式范例转换,而这里的范例转换会产生一个暂时对象,这个暂时对象具有常性,因此须要加const。 


  • C++⽀持内置范例隐式范例转换为类范例对象,须要有干系内置范例为参数的构造函数。


  • 构造函数前⾯加explicit就不再⽀持隐式范例转换。
  • 类范例的对象之间也可以隐式转换,须要相应的构造函数⽀持
在C++11后还支持多参数的范例转换,用{ ... }的方式。
如在后续使用的map中,往对象m中插入1和2时,隐式范例转换成value_type范例,即pair<...,...>

  1. #include<map>
  2. #include<iostream>
  3. using namespace std;
  4. int main()
  5. {
  6.         map<int, int> m;
  7.         m.insert({ 1,2 });
  8.         return 0;
  9. }
复制代码
static成员

   

  • ⽤static修饰的成员变量,称为静态成员变量,静态成员变量⼀定要在类外进⾏初始化。(C++规定)
  • 静态成员变量不能在声明位置给缺省值初始化,由于缺省值是给构造函数初始化列表的,静态成员变量不属于某个对象,不⾛构造函数初始化列表。
  1. class Date
  2. {
  3. private:
  4.     static int _ret;
  5. }
  6. //类外进行初始化
  7. int Date::_ret=0;
复制代码


  • 静态成员变量为全部类对象所共享,不存在对象中,存放在静态区。和平凡静态变量区别在于受public、protected、private 访问限定符的限定。
  

  

  • ⽤static修饰的成员函数,称之为静态成员函数,静态成员函数没有this指针。静态成员函数中可以访问其他的静态成员,但不能访问⾮静态的,由于没有this指针。
  • 非静态的成员函数,可以访问恣意的静态成员变量和静态成员函数。
  • 可以突破类域访问静态成员,可以通过类名::静态成员 大概 对象.静态成员 来访问静态成员变量和静态成员函数。
  求1+2+3+...+n


此题限定了for、while、if等关键词的使用,让我们无法通过递归等方式来办理。此题可以用static成员来办理题目。
界说两个static成员,一个_count来记载终极结果,_i表现1、2、3...,那么求n!时,只须要调用n次构造函数,由于是静态成员,生命周期被延伸了,使得_i能表现1、2...,每次将_i的值给_count即得终极答案。
  1. class Sum
  2. {
  3. public:
  4.     Sum()
  5.     {
  6.         _count+=_i;
  7.         _i++;
  8.     }
  9.     static int GetSum()
  10.     {
  11.         return _count;
  12.     }
  13. private:
  14.     static int _i;
  15.     static int _count;
  16. };
  17. int Sum::_i=1;
  18. int Sum::_count=0;
  19. class Solution {
  20. public:
  21.     int Sum_Solution(int n) {
  22.         Sum a[n];
  23.         return Sum::GetSum();
  24.     }
  25. };
复制代码

友元、内部类、匿名对象

友元

   

  • 友元提供了⼀种突破类访问限定符封装的⽅式,友元分为:友元函数和友元类,在函数声明大概类声明的前⾯加friend,而且把友元声明放到⼀个类的⾥⾯。
  • 既然我成为了你的朋侪,那么外部友元函数可访问类的私有和掩护成员,友元函数仅仅是⼀种声明,他不是类的成员函数。
  • 友元函数可以在类界说的任何地⽅声明,不受类访问限定符限定。 而且,⼀个函数可以是多个类的友元函数。
         上文实现Date类的流输出和流输入时就须要界说友元函数。
  1. class Date
  2. {
  3.         //友元
  4.         friend ostream& operator<<(ostream& out, const Date& d);
  5.         friend istream& operator>>(istream& in, Date& d);
  6. }
  7. ostream& operator<<(ostream& out, const Date& d);
  8. istream& operator>>(istream& in, Date& d);
复制代码


  • 友元类中的成员函数都可以是另⼀个类的友元函数,都可以访问另⼀个类中的私有和掩护成员。
  • 友元类的关系是单向的,如A类是B类的友元,但是B类不是A类的友元。
  • 友元类关系不能转达,如果A是B的友元, B是C的友元,但是A不是C的友元。
  但是小编并不发起多用友元函数,固然其提供了便利。但是友元的随意访问粉碎了封装,会增长耦合度。 在软件工程中夸大“高内聚,低耦合”的概念,耦合性越高,模块的独立性就越差。

内部类

   

  • ⼀个类可以在另⼀个类的内里,叫做内部类。内部类是⼀个独⽴的类(外部类界说的对象中不包罗内部类),区别在于受外部类类域限定和访问限定符限定。
  • 内部类默认是外部类的友元类,即内部类可以访问外部类的成员。
  • 内部类的实用范围并不是很广。只有当A类跟B类精密关联,A类实现出来重要就是给B类使⽤,那么可以思量把A类筹划为B的内部类,如果放到private/protected位置,那么A类就是B类的专属内部类,其他地方都用不了。
  匿名对象

之前在传参的时间,通常须要先界说一个著名对象再来传参。但是这过于贫苦。于是C++中界说了一个匿名对象:用范例(实参) 界说出来的对象。匿名对象⽣命周期只在当前⼀⾏,⼀般暂时界说⼀个对象当前⽤⼀下即可。
匿名对象作缺省参数

  1. class A
  2. {
  3. public:
  4.         A(int a=0)
  5.                 :_a(a)
  6.         {
  7.                 cout << "A(int a)" << endl;
  8.         }
  9.         ~A()
  10.         {
  11.                 cout << "~A()" << endl;
  12.         }
  13. private:
  14.         int _a;
  15. };
  16. void func2(const A& aa = A())
复制代码
调试可以发现匿名对象生命周期只有这一行,我们可以看到下一行他就会主动调用析构函数 。
   

固然可以通过const引用延伸匿名对象的生命周期,匿名对象跟着引用走。  
  1. const A& ref2 = A();
复制代码
编译器优化

  1. class A
  2. {
  3. public:
  4.         A(int a = 1)
  5.                 :_a1(a)
  6.         {
  7.                 cout << "A()" << endl;
  8.         }
  9.         A(const A& d)
  10.                 :_a1(d._a1)
  11.         {
  12.                 cout << "A(const A& d)" << endl;
  13.         }
  14.         A& operator=(const A& aa)
  15.         {
  16.                 cout << "A& operator=(const A& aa)" << endl;
  17.                 if (this != &aa)
  18.                 {
  19.                         _a1 = aa._a1;
  20.                 }
  21.                 return *this;
  22.         }
  23.         ~A()
  24.         {
  25.                 cout << "~A()" << endl;
  26.         }
  27. private:
  28.         int _a1 = 1;
  29. };
  30. int main()
  31. {
  32.         // 构造+拷贝-》优化为直接构造
  33.         A aa1 = 1;
  34.         return 0;
  35. }
复制代码
在上面这段步伐中,我们盼望的是1调用构造函数,然后再调用拷贝构造初始化对象aa1,但是通过调试发现调用完构造函数并未调用拷贝构造。这是为什么呢? 

  当代编译器会 为了尽大概提⾼步伐的服从 ,在不影响精确性的情况下会尽大概淘汰⼀些传参和传返   回值的过程中 可以省略的拷⻉ 。且优化情况由各个编译器处理处罚。尤其新⼀点的编译器对于一连⼀个表达式步调中的一连拷⻉会进⾏归并优化。      隐式范例,一连构造  +  拷⻉构造  ->  优化为直接构造     
  1. void f1(A aa)
  2. {
  3.     //...
  4. }
  5. int main()
  6. {
  7.         f1(1);
  8.         return 0;
  9. }
复制代码
  在上面这段步伐中,1要隐式范例转换成A类范例对象,由于是传值传参要调用拷贝构造。但是,通过调试发现仅仅调用了拷贝构造,即被编译器优化了。
   

      
  接着,我们来看看编译器对下面步伐的优化程度。
   
  1. A f2()
  2. {
  3.         A aa;
  4.         return aa;
  5. }
  6. int main()
  7. {
  8.         A aa2 = f2();
  9.         return 0;
  10. }
复制代码
在上面这段代码中(类并未写出),构造出了对象aa后,返回对象aa,由于是在f2的栈区中,不能直接返回,编译器理应会拷贝构造一个暂时对象,再将暂时对象拷贝给aa2对象。但是通过调试返回时编译器并未调用拷贝构造函数,更锋利的是在vs这个编译器下举行跨行归并优化,将构造的局部对象aa和拷贝的暂时对象和吸收返回值对象aa2优化为一个直接构造。
  接着我们在linux下运行此步伐,编译时⽤ g++ test.cpp -fno-elide-constructors可关闭编译器优化。
  可以发现编译器先是调用了构造函数,返回时调用拷贝构造给暂时对象,再将暂时对象拷贝构造给对象aa2。
  

  


回复

使用道具 举报

登录后关闭弹窗

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