C++入门☞关于类的一些特别知识点

打印 上一主题 下一主题

主题 1733|帖子 1733|积分 5199

涉及的关于类中的默认成员函数的知识点可以看我的这篇博客哦~
   C++入门必须知道的知识☞类的默认成员函数,一文讲透+运用
  目次
初始化列表
范例转换
static成员 
友元
内部类
匿名对象
 对象拷贝时的一些编译器的优化

初始化列表
我们知道类中的构造函数的使命是完成对象的初始化,使用构造函数完成初始化的方式除了在函数体内对成员变量进行赋值的方式,如下:
  1. class Date
  2. {
  3. public:
  4.     // 构造函数
  5.     Date(int year, int month, int day)
  6.     {
  7.         // 函数体内赋值初始化
  8.         _year = year;
  9.         _month = month;
  10.         _day = day;
  11.     }
  12. private:
  13.     int _year;
  14.     int _month;
  15.     int _day;
  16. };
复制代码
还有一种方式可以进行初始化——初始化列表
初始化列表的格式:

   以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个“成员变量”后面跟一个放在括号中的初始值大概表达式 
  比如上面的构造函数就可以写成下面的样子: 
  1. class Date
  2. {
  3. public:
  4.     // 初始化列表的形式
  5.     Date(int year,int month,int day)
  6.         :_year(year)
  7.         ,_month(month)
  8.         ,_day(day)
  9.     {}
  10. private:
  11.     int _yeaer;
  12.     int _month;
  13.     int _day;
  14. };
复制代码
两种初始化的区别:
   使用函数体内赋值的方式时,成员变量会先辈行默认初始化,然后再在构造函数体内被赋值。也就是在函数体内被赋值时不能称为"初始化",只能称为"赋值",初始化只能进行一次,而构造函数体内可以多次赋值;
  假如用初始化列表,则直接跳过默认初始化步骤,一步到位完成初始化
  默认初始化:指当对象被创建时,假如没有显式指定初始值,编译器会自动进行的初始化运动。具体运动取决于成员变量的范例。具体运动如下:

  • 内置范例(如 int, double, 指针等)

    • 假如变量在全局作用域(或静态存储区),默认初始化为 0/nullptr。
    • 假如变量在局部作用域(如函数内、类构造函数体内),不初始化,值是未定义的(垃圾值)。

  • 类范例(如 std::string, 自定义类)

    • 调用该类的默认构造函数(假如没有默认构造函数,会编译报错)。

两种方式在面临自定义范例的初始化时,还会有效率的差别,初始化列表的效率更高:
   使用函数体内赋值的方式,会先去调用自定义范例的构造函数,然后再调用赋值重载将构造的值赋值给成员变量
  使用初始化列表的方式,对于自定义范例只会调用一次拷贝构造函数一次性完成初始化
  初始化列表的注意事项:

   1、每个成员变量在初始化列表中只能出现一次(即初始化只能初始化一次) 
  2、类中包罗以下成员时,它们的初始化必须放在初始化列表位置进行(否则编译报错)
  

  • 引用成员变量(因为引用在定义初始化时必须赋值,且不能更改引用的对象)
  • const修饰的成员变量(const的原因与引用类似)
  • 自定义范例的成员变量,且该成员变量的类没有默认构造函数时(因为使用函数体内的方式时,对于自定义范例,会先去调用其构造函数)
  3、成员变量在类中的声明次序就是在初始化列表的初始化次序,与其在初始化列表中的位置次序无关(★),所以建议初始化列表的次序和声明的次序同等
  关于第三条:如下述代码,会得到错误的结果,因为虽然初始化列表中的次序是先初始化_a,再用_a的值初始化_b,看着是没错,但是因为成员变量声明时,是先声明的_b,再是_a,所以初始化时会先初始化_b,而不是_a,也就是初始化次序只和声明的次序相干,和初始化列表中的次序无关,但是因为_a还未初始化,所以_b的值就会是随机数
  1. class A
  2. {
  3. public:
  4.     // 初始化列表
  5.     A(int a)
  6.         :_a(a)
  7.         ,_b(_a+1)
  8.     {}
  9. private:
  10.     int _b;
  11.     int _a;
  12. };
复制代码
别的,C++11还支持在成员变量声明的位置给缺省值,这个缺省值主要是给没有表现在初始化列表初始化的成员使用的,如:
  1. class A
  2. {
  3. public:
  4.         A(int a)
  5.                 :_a1(a)
  6.         {}
  7. private:
  8.         int _a2 = 2;
  9.         int _a1;
  10. };
复制代码
此时,_a2初始化后的值就是2,相称于_a2也走了初始化列表,只不过用的声明时给的缺省值进行初始化,_a1初始化后的值就是传给参数a的值 
总结:
   尽量使用初始化列表初始化,因为无论是否表现写初始化列表,每个构造函数都有初始化列表;⽆论是否在初始化列表表现初始化,每个成员变量都要走初始化列表初始化,假如这个成员在声明位置给了缺省值,初始化列表就会用这个值进行初始化。假如你没有给缺省值,那么编译器对其的初始化是不确定的,且对于自定义范例,使用初始化列表的方式会更加高效一点
   成员变量初始化思维导图:

范例转换

   C++支持内置范例隐式范例转换为类范例的对象,必要有相干内置范例为参数的构造函数
  如下:使用一个int范例的值构造了一个A范例的对象,其中发生了隐式范例转换,将int范例转为自定义范例A
  1. class A
  2. {
  3. public:
  4.     // 以int类型为参数的相关构造函数
  5.     A(int a)
  6.         :_a(a)
  7.     {}
  8. private:
  9.     int _a;
  10. };
  11. int main()
  12. {
  13.     // 使用int类型的1构造了一个A类型的对象
  14.     A b(1);
  15.     return 0;
  16. }
复制代码
explicit关键字 
但是上述的隐式范例转换,假如你不想让它发生可以使用explicit关键字对构造函数进行修饰,那么这种隐式范例就会被禁止
  1. // 此时再想用一个数字去构造A类型的对象就会编译报错
  2. class A
  3. {
  4. public:
  5.     // 以int类型为参数的相关构造函数
  6.     explicit A(int a)
  7.         :_a(a)
  8.     {}
  9. private:
  10.     int _a;
  11. };
复制代码
对于内置范例隐式范例转换构造自定义范例时,相干内置范例的构造函数必须要有,且必要包管传入该内置范例参数时,可以构造出一个对象
除了内置范例和类范例的隐式范例转换,类范例的对象之间也可以隐式转换,必要相应的构造函数的支持
如下方的将A范例的对象隐式范例转换为B范例的对象进行构造,构造出一个临时对象用来拷贝构造B范例的对象b,但是编译器会优化,所以就酿成了直接用A范例的对象构造出了一个B范例的对象,但这个过程是因为有相干范例参数的构造函数支持才完成的
  1. class A
  2. {
  3. public:
  4.     A(int a1, int a2)
  5.         :_a1(a1)
  6.         , _a2(a2)
  7.     {}
  8.     int Get() const
  9.     {
  10.         return _a1 + _a2;
  11.     }
  12. private:
  13.     int _a1 = 1;
  14.     int _a2 = 2;
  15. };
  16. class B
  17. {
  18. public:
  19.     // 临时对象具有常性,所以需要用const接收
  20.     B(const A& a)
  21.         :_b(a.Get())
  22.     {}
  23. private:
  24.     int _b = 0;
  25. };
  26. int main()
  27. {
  28.     // { 2,2 }构造⼀个A的临时对象,再⽤这个临时对象拷⻉构造a
  29.     // 编译器遇到连续构造+拷⻉构造->优化为直接构造
  30.     A a = { 2,2 };   // C++11之后才⽀持的多参数转化
  31.     // a 隐式类型转换为b对象
  32.     // 原理跟上⾯类似
  33.     B b = a;
  34.     return 0;
  35. }
复制代码

static成员

 定义:
   被static修饰的成员称为static成员,也叫类的静态成员
  

  • 静态成员也是类的成员,受public、protected、private访问限定符的限制
  • 突破类域可以访问静态成员,可以通过类名::静态成员 大概 对象.静态成员 来访问静态成员变量和静态成员函数
  由static修饰的成员变量称为静态成员变量,由static修饰的成员函数称为静态成员函数。
  静态成员变量: 

   静态成员变量一定要在类外进行初始化,即不在构造函数初始化
  静态成员变量为类的全部对象共享,不属于某个具体的对象,不存在对象中,存放在静态区
  静态成员变量不能在声明位置给缺省值初始化,因为缺省值是给构造函数初始化列表初始化的,静态成员变量不属于某个对象,不走构造函数的初始化列表
  静态成员函数: 

   静态成员函数没有this指针
  静态成员函数中可以访问其他的静态成员,但是不能访问非静态的,因为没有this指针
  非静态成员函数可以访问恣意的静态成员变量和静态成员函数
  场景:统计一个类实例化了多少个对象——使用静态成员变量
  1. #include<iostream>
  2. using namespace std;
  3. class A
  4. {
  5. public:
  6.     A()
  7.    {
  8.      count++;
  9.    }
  10.    static int getCount()
  11.    {
  12.        return count;
  13.    }
  14. private:
  15.     static int count;
  16. };
  17. int A::count = 0;
  18. int main()
  19. {
  20.     A a1;
  21.     A a2;
  22.     A a3;
  23.     cout << "创建了"<< A::getCount() << "个A类型对象" << endl;
  24.     return 0;
  25. }
复制代码
在A范例的构造函数中使用静态成员变量count++进行计数,则每初始化一个对象就会对其进行一次++,最终就会得到实例化出的对象个数,同时注意count变量是在类外进行初始化的,只是在类内声明,且在访问静态成员时都使用了类域::静态成员变量的方式(A::count、A::getCount)
上方代码,假如黑白静态的成员函数getCount()的话,就通过对象.静态成员函数的方式得到静态成员变量count的值
  1. // 非静态成员函数
  2. int getCount()
  3. {
  4.     return count;// 返回静态成员变量
  5. }
  6. // 其余代码一致....
  7. int main()
  8. {
  9.     A a1;
  10.     A a2;
  11.     A a3;
  12.     cout << "创建了"<< a3.getCount() << "个A类型对象" << endl;
  13.     return 0;
  14. }
复制代码

友元

定义:
   友元分为友元函数和友元类,在一个类里,给函数声明大概类的声明前面加上friend的修饰,则称其为类的友元函数大概友元类,成为类的友元后可以访问类中的私有成员和保护成员
  友元类:

  1. class A
  2. {
  3.         // 友元声明,B这个类变成A的友元类
  4.         friend class B;
  5. private:
  6.         int _a1 = 1;
  7. };
  8. class B
  9. {
  10. public:
  11.         void func(const A& aa)
  12.         {
  13.                 // 友元类B就可以访问A类的私有成员
  14.                 cout << aa._a1 << endl;
  15.                 cout << _b1 << endl;
  16.         }
  17. private:
  18.         int _b1 = 3;
  19. };
  20. int main()
  21. {
  22.         A aa;
  23.         B bb;
  24.         bb.func(aa);
  25.         return 0;
  26. }
复制代码
  成为一个类的友元类后,友元类的全部成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员 
  

  • 友元关系是单向的,不具有互换性。(比如上面的B类可以访问A类的私有成员,但是A类就不可以反过来访问B类的非公有成员,因为A类不是B类的友元类)
  • 友元关系不能传递(假如C是B的友元, B是A的友元,则不能说明C时A的友元)
  • 友元关系不能继承(涉及继承知识)
  友元函数:

友元函数可以直接访问类的私有成员,它是定义在类外部的平凡函数,不属于任何类,即不属于类的成员函数,但必要在类的内部声明,声明时必要加friend关键字,如下
  1. class Date
  2. {
  3.     // 友元函数声明
  4.     friend ostream& operator<<(ostream& _cout, const Date& d);
  5.     friend istream& operator>>(istream& _cin, Date& d);
  6. public:
  7.     Date(int year = 1900, int month = 1, int day = 1)
  8.         : _year(year)
  9.         , _month(month)
  10.         , _day(day)
  11.     {}
  12. private:
  13.     int _year;
  14.     int _month;
  15.     int _day;
  16. };
  17. // 自定义Date类的输入输出
  18. ostream& operator<<(ostream& _cout, const Date& d)
  19. {
  20.     _cout << d._year << "-" << d._month << "-" << d._day;
  21.     return _cout;
  22. }
  23. istream& operator>>(istream& _cin, Date& d)
  24. {
  25.     _cin >> d._year;
  26.     _cin >> d._month;
  27.     _cin >> d._day;
  28.     return _cin;
  29. }
  30. int main()
  31. {
  32. Date d;
  33. cin >> d;
  34. cout << d << endl;
  35. return 0;
  36. }
复制代码
  1、友元函数可以在类定义的恣意地方声明(注意:是在类定义的地方声明,类的定义有声明和定义都在一个文件中实现的方式,也有声明定义分离的方式,假如是分离的方式,要在定义的地方声明友元函数),且它不受任何访问限定符的限制
  2、一个函数可以是多个类的友元函数
  友元提供了一种突破封装的方式,偶然提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多用

内部类

定义:
   假如一个类定义另一个类的内部,这个内部的类就叫做内部类。内部类是一个独立的类,跟定义在全局相比,他只是受外部类类域限制和访问限定符限制,所以它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越的访问权限。
  注意:
   

  • 内部类默认就是外部类的友元类,内部类可以通过外部类的对象参数来访问外部类中的全部成员。但是外部类不是内部类的友元。
  • 内部类可以访问的外部的全部成员中包括外部类的静态成员,不必要外部类的对象/类名来突破类域访问
  • sizeof(外部类)=外部类,和内部类没有任何关系。
  内部类本质也是一种封装,当A类跟B类紧密关联,A类实现出来主要是给B类使用时,可以思量将其设为B类的内部类, 假如放到private/protected位置,那么A类就是B类的专属内部类,其他地方都用不了。

匿名对象

定义:
   用范例(实参) 定义出来的对象叫做匿名对象,前面我们使用 范例 对象名(实参) 格式定义出来的叫有名对象
  1. // 假设有一个名为A的类,且可以用int类型隐式转换初始化
  2. int main()
  3. {
  4.     A aa1(1);// 有名对象
  5.     A(1);// 匿名对象
  6. }
复制代码
假如A类的构造是可以无参的,那么A类的匿名对象的创建也不可以省略掉括号,否则语法出错
  1. A();// 无参的A类型匿名对象
复制代码
注意:匿名对象的生命周期只在当前一行,一般临时定义一个对象当前用一下即可,就可以使用匿名对象

 匿名对象调用场景:
 ①成员函数调用:
  1. class Solution {
  2. public:
  3.         int Sum_Solution(int n) {
  4.                 //...
  5.                 return n;
  6.         }
  7. };
  8. int main()
  9. {
  10.     // 没有匿名对象时候,需要写两行调用
  11.     Solution s1;
  12.     cout << s1.Sum_Solution(10) << endl;
  13.     // 有了匿名对象直接写成一行调用
  14.     cout << Solution().Sum_Solution(10) << endl;
  15. }
复制代码
 ②函数参数自定义范例,必要给参数缺省值:给匿名对象
  1. void func(A aa = A(1))
  2. {}
复制代码
 延长匿名对象生命周期:
 引用匿名对象,延长到和引用的生命周期同等,
 但是匿名对象和临时对象一样具有常性,要加const
  1. const A& r = A();
复制代码
匿名对象就像一个一次性的事物,用完就可丢~

 对象拷贝时的一些编译器的优化

   现代编译器会为了尽可能提⾼程序的效率,在不影响正确性的情况下会尽可能减少⼀些传参和传返回值的过程中可以省略的拷贝
  假设一个类的定义如下:
  1. class A
  2. {
  3. public:
  4.     // 构造函数
  5.         A(int a = 0)
  6.                 :_a1(a)
  7.         {
  8.                 cout << "A(int a)" << endl;
  9.         }
  10.         // 拷贝构造
  11.         A(const A& aa)
  12.                 :_a1(aa._a1)
  13.         {
  14.                 cout << "A(const A& aa)" << endl;
  15.         }
  16.     // 赋值重载
  17.         A& operator=(const A& aa)
  18.         {
  19.                 cout << "A& operator=(const A& aa)" << endl;
  20.                 if (this != &aa)
  21.                 {
  22.                         _a1 = aa._a1;
  23.                 }
  24.                 return *this;
  25.         }
  26.     // 析构函数
  27.         ~A()
  28.         {
  29.                 cout << "~A()" << endl;
  30.         }
  31. private:
  32.         int _a1 = 1;
  33. };
复制代码
传参时的编译器优化:

连续构造+拷贝构造优化——>直接构造,省略了一次拷贝
  1. void f1(A aa)
  2. {}
  3. int main()
  4. {
  5.     // 优化
  6.     // 按理这里会先构造临时对象,再把临时对象拷贝构造给aa0
  7.     // 但是这里优化成直接用1进行构造了
  8.     A aa0 = 1;// 隐式类型转换
  9.     cout << endl;
  10.     // 优化
  11.     // 按理是用1构造一个匿名对象,再把匿名对象拷贝构造给aa
  12.     // 隐式类型转换,连续构造+拷贝构造->优化为直接构造
  13.     f1(1);
  14.     // 优化
  15.     // 一个表达式中,连续构造+拷贝构造->优化为一个构造
  16.     f1(A(2));
  17.     cout << endl;
  18.     return 0;
  19. }
复制代码
传值返回时的编译器优化:

两次连续的拷贝构造优化成一次拷贝构造
  1. A f2()
  2. {
  3.         A aa;
  4.         return aa;// 不会返回aa,会用aa创建一个临时对象,返回临时对象
  5. }
  6. // 传值返回的优化
  7. int main()
  8. {
  9.    
  10.     // 按理需要先用返回值aa拷贝构造一个临时对象,再用临时对象拷贝构造aa2
  11.         // 优化:两个拷贝构造合二为一为一个拷贝构造
  12.     // 更新的编译器优化:直接优化成一次构造,直接用aa的值构造aa2
  13.         A aa2 = f2();
  14.         cout << endl;
  15.         // 按理是返回值aa拷贝构造一个临时对象,再用临时对象赋值拷贝给aa1
  16.     // 优化:直接用aa的值赋值拷贝给aa1
  17.     A aa1 = 1;
  18.         aa1 = f2();
  19.         cout << endl;
  20.         return 0;
  21. }
复制代码



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

本帖子中包含更多资源

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

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

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

本版积分规则

美丽的神话

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表