前言
这一篇文章是上一篇的续集(这里有上篇链接)前面我们讲了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 访问限定符的限制
🚨静态成员可以被类的所有对象共享,所以在修改静态数据成员时应谨慎考虑它们的影响范围。
🚨静态数据成员在类外部定义和初始化,并且需要在类的声明中进行声明。
🚨静态函数成员不能访问非静态的数据成员,因为静态函数成员不与任何特定对象关联。