【C++入门到精通】C++入门 —— 类和对象(初始化列表、Static成员、友元、内部类、匿名对象)上

简介: 【C++入门到精通】C++入门 —— 类和对象(初始化列表、Static成员、友元、内部类、匿名对象)上

前言


       这一篇文章是上一篇的续集(这里有上篇链接)前面我们讲了C语言的基础知识,也了解了一些数据结构,并且讲了有关C++的命名空间的一些知识点以及关于C++的缺省参数、函数重载,引用 和 内联函数。也相信大家都掌握的不错,接下来博主将会带领大家继续学习有关C++比较重要的知识点——类和对象(初始化列表、Static成员、友元、内部类、匿名对象)。下面话不多说坐稳扶好咱们要开车了。


一、初始化列表


⭕初始化列表概念


       在C++中,初始化列表是一种在对象或类的构造函数中初始化成员变量的特殊语法。它在构造函数的参数列表之后(详细介绍构造函数),使用冒号分隔,后跟成员初始化列表。


初始化列表的语法如下所示:


ConstructorName(Initialization1, Initialization2, ...)

       其中, ConstructorName 是构造函数的名称, Initialization 是成员的初始化,可以包括成员变量、常量、引用以及调用其他构造函数等。


⭕初始化列表的优点


1. 初始化成员变量:使用初始化列表可以直接在构造函数中初始化成员变量,而不需要在构造函数体内分别对每个成员进行赋值。

2. 常量成员初始化:对于类中的常量成员,只能在初始化列表中进行初始化,而不能在构造函数体内赋值。

3. 避免无效构造:初始化列表可以避免在构造函数体内对成员变量进行默认初始化,然后再赋予新值的过程,从而提高效率。

4. 初始化顺序控制:使用初始化列表可以控制成员变量的初始化顺序,而不仅仅是它们在类中的声明顺序。


下面这段代码,展示了如何在构造函数中使用初始化列表初始化成员变量:


class MyClass {
private:
  int num;
  double value;
public:
  MyClass(int n, double v) 
        : num(n) 
        , value(v) 
    {
    // 构造函数体
    }
};


       在上述示例中, MyClass 类的构造函数使用初始化列表初始化了成员变量 num 和 value ,分别使用参数 n 和 v 来进行初始化。


       注意:冒号后面的代码就是初始化列表,其中 num(n) 表示将参数 n 的值赋给成员变量 num , value(v) 表示将参数 v 的值赋给成员变量 value 。


⭕使用场景


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


2、类中包含以:引用成员变量,const成员变量,自定义类型成员(且该类没有默认构造函数时)必须放在初始化列表位置进行初始化。


class A
{
public:
    A(int a)
    :_a(a)
    {
    }
private:
    int _a;
};
class B
{
public:
    B(int a, int ref)
        :_aobj(a)
        ,_ref(ref)
        ,_n(10)
    {
    }
private:
    A _aobj; // 没有默认构造函数
    int& _ref; // 引用
    const int _n; // const
};


3、尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化。


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


⭕explicit关键字


       在C++中,explicit 是一个关键字,用于修饰类的构造函数。它的主要作用是防止隐式类型转换,限制只能进行显示(显式)的类型转换,对于单个参数或者除第一个参数无默认值其余均有默认值的构造函数,还具有类型转换的作用。


       当一个构造函数被声明为 explicit 时,它将不会被用于隐式转换。这意味着在使用该构造函数创建对象时,不能使用隐式转换来将参数类型转换为构造函数所需的类型,而必须使用显式的方式进行类型转换。


下面我会用几行代码来说明 explicit 关键字的使用:


class MyClass {
private:
  int num;
public:
  explicit MyClass(int n) : num(n) {
    // 构造函数体
  }
};
int main() {
  MyClass obj1(5);      // 直接调用构造函数,隐式转换不起作用
  MyClass obj2 = 10;   // 错误!使用了隐式转换, explicit构造函数无法隐式地将int类型转换为MyClass类型
  MyClass obj3 = MyClass(10); // 正确!使用显式转换
  return 0;
}


       在上述示例中, MyClass 类的构造函数被声明为 explicit ,当我们创建对象时,不能使用隐式转换方式将整型参数转换为 MyClass 类型的对象。因此, MyClass obj2 = 10; 这行代码会引发编译错误。而 MyClass obj3 = MyClass(10); 这行代码是合法的,它使用显式转换方式将整型参数转换为 MyClass 类型的对象。


二、Static成员


⭕Static成员概念


       静态(static)成员是类的成员,而不是对象的成员。静态成员在类的所有对象之间共享,并且它们不与任何特定的对象关联,可以将静态成员分为两个类型:静态数据成员和静态函数成员。


🔴静态数据成员:


       静态数据成员是与类关联的变量,而不是与类的对象关联的。它们存储在类的一个独立的存储区域中,而不是存储在类的对象中。静态数据成员在类的所有对象之间共享。可以通过类名加作用域解析运算符(::)来访问静态数据成员。


  静态数据成员需要在类的声明中进行定义,并且在类外进行初始化。例如:


class MyClass 
   {
   private:
     static int count; // 静态数据成员的声明
   public:
     // 静态数据成员的初始化
     static int initialize;
     // ...
   };
   int MyClass::count = 0; // 静态数据成员的定义和初始化

静态数据成员被所有类的对象共享,因此它们的值在多个对象之间是共享的。


🔴静态函数成员:


       静态函数成员是与类关联的函数,而不是与类的对象关联的。静态函数成员可以在不创建类的对象的情况下被调用,通过使用类名加作用域解析运算符(::)来访问静态函数成员。


  静态函数成员可以访问类的静态数据成员,但不能访问非静态的数据成员。静态函数成员在类的对象上操作的是静态成员,而不是对象的特定实例。


class MyClass {
   private:
     static int count; // 静态数据成员的声明
   public:
     static void increment() {
       count++;   // 静态函数成员可以访问静态数据成员
     }
   };
   int MyClass::count = 0; // 静态数据成员的定义和初始化
   int main() {
     MyClass::increment(); // 调用静态函数成员
     return 0;
   }


静态函数成员不需要通过类的对象进行调用,而是直接通过类名调用。


⭕使用静态成员的优点


       静态成员能够提供在类的所有对象之间共享的数据、全局访问的能力、一致性和效率的优势,以及更好的命名空间管理。这使得它们在某些情况下非常有用,并可以提高代码的可维护性和性能。


       1. 共享数据:静态数据成员在类的所有对象之间共享,它们只有一个副本。这意味着无论创建了多少个对象,它们都可以访问和修改同一个静态数据成员,从而实现数据的共享。这对于存储一些在类的所有对象中都具有相同值或状态的数据非常有用。例如,可以使用静态成员来记录某个类实例的数量,或者作为全局设置信息的存储器。


       2. 全局访问:静态数据成员和静态函数成员都可以在不创建类对象的情况下直接访问和调用。这使得它们可以在类的外部被其他类、函数或文件访问,并且不需要通过类的对象进行访问。这提供了一种全局访问数据和功能的方式,而无需创建类对象。例如,可以通过类名访问静态数据成员来获取全局配置信息,或者直接调用静态函数成员来执行某些全局操作。


       3. 一致性和效率:静态数据成员在整个类的对象之间保持一致的值,无论创建了多少个对象,它们始终具有相同的状态。这可以提高代码的一致性和可维护性。另外,由于静态数据成员只有一个副本,因此可以节省内存空间。而静态函数成员在调用时无需创建类的对象,可以直接通过类名调用,提高了代码的效率。


       4. 访问权限控制:静态成员可以被用于实现一些对于类的所有对象具有一致性的配置、计数或限制。通过将这些成员声明为私有的,可以确保只有类的成员函数可以访问和修改它们,从而保证了对其状态的控制。这允许在类中进行一些特殊的操作,可以确保只有类内部的特定成员函数能够对静态成员进行操作和修改,而外部代码无法直接访问。


       5. 命名空间扩展:静态成员可以用于扩展类的命名空间。通过在类中添加静态成员,可以将相关的函数和数据组织在一起,提供更好的命名空间管理,避免全局名称冲突。这使得代码更具可读性和可维护性,因为相关的函数和数据在类的范围内是分组的,并且可以通过类名进行访问。


⭕使用静态成员的注意事项


🚨静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区

🚨静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明

🚨类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问

🚨静态成员函数没有隐藏的this指针,不能访问任何非静态成员

🚨静态成员也是类的成员,受public、protected、private 访问限定符的限制


🚨静态成员可以被类的所有对象共享,所以在修改静态数据成员时应谨慎考虑它们的影响范围。

🚨静态数据成员在类外部定义和初始化,并且需要在类的声明中进行声明。

🚨静态函数成员不能访问非静态的数据成员,因为静态函数成员不与任何特定对象关联。


目录
相关文章
|
2天前
|
编译器 C++
C++ 类构造函数初始化列表
构造函数初始化列表以一个冒号开始,接着是以逗号分隔的数据成员列表,每个数据成员后面跟一个放在括号中的初始化式。
42 30
|
2月前
|
编译器 C++
【C++】详解初始化列表,隐式类型转化,类静态成员,友元
【C++】详解初始化列表,隐式类型转化,类静态成员,友元
|
3月前
|
存储 编译器 C++
【C++】类和对象④(再谈构造函数:初始化列表,隐式类型转换,缺省值
C++中的隐式类型转换在变量赋值和函数调用中常见,如`double`转`int`。取引用时,须用`const`以防修改临时变量,如`const int& b = a;`。类可以有隐式单参构造,使`A aa2 = 1;`合法,但`explicit`关键字可阻止这种转换。C++11起,成员变量可设默认值,如`int _b1 = 1;`。博客探讨构造函数、初始化列表及编译器优化,关注更多C++特性。
|
1月前
|
C++ 容器
C++中自定义结构体或类作为关联容器的键
C++中自定义结构体或类作为关联容器的键
32 0
|
16天前
|
存储 编译器 C++
C ++初阶:类和对象(中)
C ++初阶:类和对象(中)
|
1月前
|
存储 安全 编译器
【C++】类和对象(下)
【C++】类和对象(下)
【C++】类和对象(下)
|
16天前
|
C++
C++(十六)类之间转化
在C++中,类之间的转换可以通过转换构造函数和操作符函数实现。转换构造函数是一种单参数构造函数,用于将其他类型转换为本类类型。为了防止不必要的隐式转换,可以使用`explicit`关键字来禁止这种自动转换。此外,还可以通过定义`operator`函数来进行类型转换,该函数无参数且无返回值。下面展示了如何使用这两种方式实现自定义类型的相互转换,并通过示例代码说明了`explicit`关键字的作用。
|
16天前
|
存储 设计模式 编译器
C++(十三) 类的扩展
本文详细介绍了C++中类的各种扩展特性,包括类成员存储、`sizeof`操作符的应用、类成员函数的存储方式及其背后的`this`指针机制。此外,还探讨了`const`修饰符在成员变量和函数中的作用,以及如何通过`static`关键字实现类中的资源共享。文章还介绍了单例模式的设计思路,并讨论了指向类成员(数据成员和函数成员)的指针的使用方法。最后,还讲解了指向静态成员的指针的相关概念和应用示例。通过这些内容,帮助读者更好地理解和掌握C++面向对象编程的核心概念和技术细节。
|
29天前
|
存储 算法 编译器
c++--类(上)
c++--类(上)
|
1月前
|
编译器 C++
virtual类的使用方法问题之C++类中的非静态数据成员是进行内存对齐的如何解决
virtual类的使用方法问题之C++类中的非静态数据成员是进行内存对齐的如何解决