C++修炼:类和对象(下)

王柳  金牌会员 | 2025-3-19 15:35:42 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 991|帖子 991|积分 2973

        Hello大家好!很高兴我们又见面啦!给生活添点passion,开始今天的编程之路!
  

  我的博客:<但凡.
  我的专栏:《编程之路》、《数据布局与算法之美》、《题海拾贝》、《C++修炼之路》
  欢迎点赞,关注!
  目次
 
1、 初始化列表
2、类型转换
3、取地址运算符重载
4、static成员
5、友元
6、内部类
7、匿名对象

 
1、 初始化列表

        我们在实例化类的对象的时候都是调用构造函数来举行初始化的。但之前我们都是把初始化的成员函数放在函数体内赋值。在这里我们介绍一种新的赋值方式,就是初始化列表。
函数体内赋值:
  
  1. DATE(int year = 2000, int mouth = 11, int day = 1)
  2. {
  3.         _year = year;
  4.         _mouth = mouth;
  5.         _day = day;
  6. }
复制代码
初始化列表:
  
  1. DATE(int year = 2000, int mouth = 11, int day = 1)
  2.         :_year(year)
  3.         , _mouth(mouth)
  4.         , _day(day)
  5. {
  6.        
  7. }
复制代码
以上是两种初始化方式的对比。      
        使用初始化列表的格式是以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式。
        初始化列表是每个成员变量举行初始化的位置,无论是否表现写初始化列表,每个成员初始化都须要走初始化列表。而且每个成员变量只能在初始化列表出现一次。
        也就是说就算我们写成函数体内初始化的样子,成员变量初始化时也是会走初始化列表的。
        如果我们写成如下代码的样子,实在初始化时是先走初始化列表,再走函数体,而函数体里面的再走初始化列表。那倒不如直接都写到促使华列表里面。
  1. DATE(int year = 2000, int mouth = 11, int day = 1)
  2.         :_year(year)
  3.         , _mouth(mouth)
  4.        
  5. {
  6.         _day = day;
  7. }
复制代码
        引用成员变量,const成员变量,没有默认构造的类类型变量,必须放在初始化列表位置举行初始化(或给定缺省值),否则会编译报错。
        然后再介绍一点,C++11支持在成员变量声明时给定缺省值。这个缺省值是给没有表现在初始化列表里的成员变量使用的。
  1. #include<iostream>
  2. using namespace std;
  3. class DATE
  4. {
  5. public:
  6.         DATE(int x)
  7.                 :t(x)
  8.         {
  9.                 cout << t<<" "<<d << endl;
  10.         }
  11. private:
  12.         int t = 1;
  13.         int d = 2;
  14. };
  15. int main()
  16. {
  17.         DATE ti(0);
  18.         return 0;
  19. }
复制代码
        输出结果为0和2,也就是说当我们不写成初始化列表时成员变量的值就是缺省值,如果给定新的值就用新的值走初始化列表。
如今我们梳理一下成员变量走初始化列表的逻辑:
           1、表现在初始化列表的成员变量就拿这个值举行初始化。
   
          2、对于不表现在初始化列表的成员变量按他的缺省值初始化。
   
          3、对于既没表现在初始化列表的,也没有缺省值的,我们分以下三种情况来讨论:
   
          (1)如果说内置成员变量,大概会初始化为0或者随机值,根据编译器来定。
   
          (2)如果是自界说类成员,调用该类的默认构造函数。
   
          (3)对于引用成员变量,const成员变量,没有默认构造函数的成员变量,必须写在初始化列表或给定缺省值,不然编译报错。
           初始化列表中按照成员变量在类中声明顺序举行初始化,跟成员在初始化列表出现的的先后顺序无关。建声明顺序和初始化列表顺序保持一致。
下面我们来看一到题巩固一下:
  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. }
  11. void Print()
  12. {
  13.      cout << _a1 << " " << _a2 << endl;
  14. }
  15. private:
  16.      int _a2 = 2;
  17.      int _a1 = 2;
  18. };
  19. int main()
  20. {
  21.      A aa(1);
  22.      aa.Print();
  23. }
复制代码
         上面这代码的输出结果是什么?
         先说答案,说出结果是输出1和随机值。
        分析一下,起首这道题和缺省值没什么关系,因为既然a1,a2都表现在初始化列表里面了那就不须要用缺省值走初始化列表了。如今须要注意一点,初始化的顺序和声明的顺序保持一致。那也就是说先初始化a2,再初始化a1。那如许的话a2在初始化的时候他的值和a1的值相等,而a1此时的值是随机值,在这之后a1举行初始化,他的值等于1.
        颠末上述分析,输出结果为1和随机值。
2、类型转换

        C++支持内置类型隐式类型转换为类类型对象,须要有相干内置类型为参数的构造函数。
       构造函数前面加explicit就不再支持隐式类型转换。
       类类型的对象之间也可以隐式转换,须要相应的构造函数支持。
        下面我们看如许一串代码:
  1. #include<iostream>
  2. using namespace std;
  3. class A
  4. {
  5. public:
  6.         A(int x)
  7.                 :t(x)
  8.         {
  9.         }
  10.         void print()
  11.         {
  12.                 cout << t << endl;
  13.         }
  14. private:
  15.         int t = 1;
  16. };
  17. int main()
  18. {
  19.         A tt=10;
  20.         tt.print();
  21.         return 0;
  22. }
复制代码
        对于A tt=10这句代码,我上一期提到过,这也是调用构造函数而不是=的运算符重载。但是这种构造并不是简单的构造。他实在是先用10构造出一个暂时对象,在把暂时对象拷贝构造给tt(这里用的拷贝构造是编译器本身生成的拷贝构造),但是颠末编译器优化之后,优化成了直接构造。这实在就是隐式类型转换。
  1. #include<iostream>
  2. using namespace std;
  3. class A
  4. {
  5. public:
  6.         A(int x)
  7.                 :t(x)
  8.         {
  9.         }
  10.         void print()
  11.         {
  12.                 cout << t << endl;
  13.         }
  14. private:
  15.         int t = 1;
  16. };
  17. int main()
  18. {
  19.         A tt=10;
  20.         tt.print();
  21.         A& tt1 = tt;
  22.         tt1.print();
  23.         const A& tt2 = 1;
  24.         return 0;
  25. }
复制代码
         如今我又把上面的代码新增了两句,对于const A& tt2=1;这一句,如果我们去掉const就会发现编译报错,这是为什么?
        我们来分析一下。起首1创建暂时对象,由于暂时对象具有常性,如果直接用A&的话就会造成权限放大的标题,编译报错。所以我们必须const引用。
        隐式类型转换实际上是极大水平方便我们编写步伐的。如今想像如许一个场景,如果我们新学习了一个数据布局叫栈,栈里面插入的数据都是A类型的,那我们push的时候是先构造一个A类型的数据,再把A传入push函数,照旧直接把这个数据传入更方便呢?肯定是让他本身举行隐式类型转换,直接传入数据更方便的。
3、取地址运算符重载

        为什么我会提到这个东西呢?不知道大家发现没有我在上面的代码中并没有打印tt2,因为如果直接调用print函数的话会报错:

        因为我们的print函数里面的this指针类型和constA&并不匹配。
        起首我们的printf函数里的this指针实际上应该是A*  const  this。这里的const意思是限定this的指向不能改变。他说到底照旧A*类型的。那这不对啊,我们调用的时候传入的参数实际上是const A*类型的,这又是权限放大了。那应该怎么办呢?
        我们对打印函数举行升级:
  1. void print() const
  2. {
  3.         cout << t << endl;
  4. }
复制代码
        这个操纵就是把this指针的类型改成了const A* const this。如许我们所有的变量都可以正常打印了。 
4、static成员

        用static修饰的成员变量,称之为静态成员变量。这个大家应该都知道,因为我们在C语言阶段就打仗过static了。
        静态成员变量⼀定要在类外举行初始化。
        静态成员存放在静态区,不在类对象中。正因此我们在举行如下代码时是会报错的:
  1. class A
  2. {
  3. public:
  4.         A(int x)
  5.                 :t(x)
  6.         {
  7.         }
  8.         void print() const
  9.         {
  10.                 cout << t << endl;
  11.         }
  12. private:
  13.         int t = 1;
  14.         static int b = 5;
  15. };
复制代码

        大家可以如许明白,我们static成员都不在类中存储,那他初始化实在根本就不消走初始化列表,那我们怎么能过他给缺省值呢? 也正因此,我们的静态成员变量在类外举行初始化。
  1. #include<iostream>
  2. using namespace std;
  3. class A
  4. {
  5. public:
  6.         A(int x)
  7.                 :t(x)
  8.         {
  9.         }
  10.         void print() const
  11.         {
  12.                 cout << t <<" "<< b << endl;
  13.         }
  14. private:
  15.         int t = 1;
  16.         static int b;
  17. };
  18. int A::b = 100;
  19. int main()
  20. {
  21.         A tt=10;
  22.         tt.print();
  23.        
  24.         return 0;
  25. }
复制代码
 以上代码正确的对静态成员b举行了类外初始化。
        对于静态成员的访问,如果是公有的情况下,tt.b和A::b都可以举行访问。但是如果是私有成员就不能如许随便访问了。因为静态成员也受private等的限定。
        如果在函数返回值类型前加上static那么他就是一个静态函数。静态函数不能访问非静态的成员变量,因为他没有this指针。但是可以访问静态的成员变量。
        如今我们把print函数改成了静态函数,那我们须要对这个函数举行改造才能正常使用:
  1. #include<iostream>
  2. using namespace std;
  3. class A
  4. {
  5. public:
  6.         A(int x)
  7.                 :t(x)
  8.         {
  9.         }
  10.         static void print(const A& tt)
  11.         {
  12.                 cout << tt.t <<" "<< b << endl;
  13.         }
  14. private:
  15.         int t = 1;
  16.         static int b;
  17. };
  18. int A::b = 100;
  19. int main()
  20. {
  21.         A tt=10;
  22.         tt.print(tt);
  23.        
  24.         return 0;
  25. }
复制代码
         如果函数外打印静态成员的话,我们可以通过调用函数简介访问:
  1. static int getnum()
  2. {
  3.         return b;
  4. }
复制代码
5、友元

        友元是一种声明,可以用过友元声明突破访问限定。
  1. #include<iostream>
  2. using namespace std;
  3. class A
  4. {
  5. public:
  6.         A(int x)
  7.                 :t(x)
  8.         {
  9.         }
  10.         friend int add(const A& a);
  11.        
  12. private:
  13.         static int b;
  14.         int t = 1;
  15. };
  16. int add(const A& a)
  17. {
  18.         return a.b + a.t;
  19. }
  20. int A::b = 100;
  21. int main()
  22. {
  23.         A tt = 10;
  24.         cout << add(tt) << endl;
  25.         return 0;
  26. }
复制代码
        比方上面这串代码,我们把add函数声明成友元,那么add函数就可以访问A里面的私有成员。
        我们也可以把一个类界说成另一个类的友元:
  1. #include<iostream>
  2. using namespace std;
  3. class B
  4. {
  5.         friend class A;
  6. private:
  7.         int r = 5;
  8. };
  9. class A
  10. {
  11. public:
  12.         A(int x)
  13.                 :t(x)
  14.         {
  15.         }
  16.         void print(const B& R)
  17.         {
  18.                 cout << R.r << endl;
  19.         }
  20.        
  21. private:
  22.         static int b;
  23.         int t = 1;
  24. };
  25. int A::b = 100;
  26. int main()
  27. {
  28.         A tt = 10;
  29.         B bb;
  30.         tt.print(bb);
  31.         return 0;
  32. }
复制代码
        我们把classA 声明为B的友元,就可以通过A突破访问限定符直接访问B的私有成员。
        须要注意的是,友元类的关系是单向的,不具有交换性,好比A类是B类的友元,但是B类不是A类的友元。友元类关系不能传递,如果A是B的友元,B是C的友元,但是A不是C的友元。
        我们肯定要弄清谁是谁的友元,好比A是B的友元,那么就可以通过A访问B。顺序不能反。
另外就是上面两串代码中都是声明友元的那一部分放在上面(先界说)。
        友元这个东西大家了解就好,因为友元会粉碎封装,不推荐大量使用。
6、内部类

        我们可以把一个类声明在另一个类里面,如许的声明下处在别的类内部的类就叫内部类
        内部类是外部类的友元。如果我们把内部类声明为外部类的私有成员,那么这个内部类就可以成为外部类的私有类。
  1. class A
  2. {
  3. private:
  4.      static int _k;
  5.      int _h = 1;
  6. public:
  7. class B // B默认就是A的友元
  8. {
  9.      public:
  10.      void foo(const A& a)
  11.      {
  12.          cout << _k << endl; //OK
  13.          cout << a._h << endl; //OK
  14.      }
  15.      int _b1;
  16.      };
  17. };
  18. int main()
  19. {
  20.     A::B b;
  21.    
  22. }
复制代码
7、匿名对象

        用类型界说出来的对象叫匿名对象,匿名对象生命周期只有一行。
  1. #include<iostream>
  2. using namespace std;
  3. class B
  4. {
  5.         friend class A;
  6. private:
  7.         int r = 5;
  8. };
  9. class A
  10. {
  11. public:
  12.         A(int x=8)
  13.                 :t(x)
  14.         {
  15.         }
  16.         void print(const B& R)
  17.         {
  18.                 cout << R.r << endl;
  19.         }
  20.        
  21. private:
  22.         static int b;
  23.         int t = 1;
  24. };
  25. int A::b = 100;
  26. int main()
  27. {
  28.        
  29.         B bb;
  30.         A().print(bb);
  31.         return 0;
  32. }
复制代码
        我们把上一段代码稍加改作育是匿名对象的使用场景。好比在上面的代码中我们就想调用一下print,那就用匿名对象调用就行了,没须要搞出来一个有名对象。
        须要注意的是匿名对象的构造须要调用我们的构造函数,也就是说我们的写出来这个构造函数,而且构造函数参数里面要给缺省值,不然匿名对象无法创建会报错。
        好了,今天的内容就分享到这,我们下期再见!
 

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

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

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

王柳

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表