【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 访问限定符的限制?静态成员可以被类的所有对象共享,所以在修改静态数据成员时应谨慎考虑它们的影响范围。
?静态数据成员在类外部定义和初始化,并且需要在类的声明中进行声明。
?静态函数成员不能访问非静态的数据成员,因为静态函数成员不与任何特定对象关联。
三、友元
⭕友元的概念
友元(friend)是一种机制,允许一个类或函数访问另一个类的私有成员。通过友元关系,可以将某个外部实体(类或函数)授权以特殊的方式访问另一个类的非公开成员,而不需要违反封装的原则,友元可以分为两种类型:类友元和函数友元。
注意:友元关系是单向的。例如,如果类A声明了类B为友元,使得B可以访问A的私有成员,这并不意味着A能够自动访问B的私有成员。友元关系需要在每个需要访问私有成员的类或函数中单独声明。友元关系可以破坏封装性,因为它使得另一个类或函数可以访问私有成员。因此,应该谨慎使用友元机制,只在确实需要访问私有成员的情况下使用。同时,友元应尽可能地减少,以保持良好的封装性和代码可读性。
⭕类友元
可以将一个类声明为另一个类的友元类。这将使得友元类可以访问被授权类的私有成员。在类的定义中使用 friend
关键字声明友元类,并且该声明通常放在被授权类的私有部分或公有部分的起始位置,被授权类的所有成员对友元类的所有成员都具有访问权限。
class MyClass {
private:
// 声明友元类
friend class FriendClass;
int privateData;
public:
// 公有成员
};
class FriendClass {
public:
void accessPrivateData(const MyClass& obj) {
// 可以访问MyClass的私有成员
int data = obj.privateData;
}
};
在上面的代码中,FriendClass
被声明为 MyClass
的友元类,因此在 FriendClass
中可以访问 MyClass
的私有成员 privateData
⭕函数友元
可以将一个函数声明为另一个类的友元函数。这将使得友元函数可以直接访问被授权类的私有成员。在类的定义中使用 friend
关键字声明友元函数。被授权类的所有成员对友元函数具有访问权限。
class MyClass {
private:
int privateData;
public:
// 声明友元函数
friend void friendFunction(const MyClass& obj);
};
void friendFunction(const MyClass& obj) {
// 可以访问MyClass的私有成员
int data = obj.privateData;
}
在上面的代码中,friendFunction
被声明为 MyClass
的友元函数,因此在 friendFunction
中可以直接访问 MyClass
的私有成员 privateData
友元是一种特殊的关系,允许一个类或函数访问另一个类的私有成员。友元可以是类或函数,并通过使用 friend
关键字进行声明。友元关系在某些情况下是有用的,但应该谨慎使用。
四、内部类
⭕内部类的概念
如果一个类定义在另一个类的内部,这个内部类就叫做内部类。内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越的访问权限。
注意:内部类就是外部类的友元类,参见友元类的定义,内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元。
⭕内部类的特点
1. 内部类可以定义在外部类的public、protected、private都是可以的。
2. 注意内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名。
3. sizeof(外部类)=外部类,和内部类没有任何关系。
class A
{
private:
static int k;
int h;
public:
class B // B天生就是A的友元
{
public:
void foo(const A& a)
{
cout << k << endl;//OK
cout << a.h << endl;//OK
}
};
};
int A::k = 1;
int main()
{
A::B b;
b.foo(A());
return 0;
}
五、匿名对象
⭕匿名对象概念
匿名对象指的是在没有被命名的情况下创建的临时对象。它们在表达式中被直接使用,并且通常用于一次性的操作或作为函数调用的参数。匿名对象的创建方式是在类名后紧跟一对小括号,即类名后面加上一对空括号,或者在构造函数后调用无参构造函数。
class MyClass {
public:
MyClass() {
// 构造函数代码
}
void doSomething() {
// 成员函数代码
}
};
int main() {
// 创建匿名对象并调用成员函数
MyClass().doSomething();
// 将匿名对象作为函数参数
someFunction(MyClass());
return 0;
}
在上面的代码中,MyClass().doSomething()
创建了一个匿名对象,并在该对象上调用了doSomething()
成员函数。同样地,someFunction(MyClass())
将匿名对象作为函数someFunction
的参数。
匿名对象是在没有被命名的情况下直接使用的临时对象。它们适用于一次性操作或作为函数调用的参数,并且通常用于临时任务。匿名对象的生命周期仅限于所在的表达式。但要注意匿名对象没有具名对象的灵活性和可重用性,因此在需要引用对象或保留对象状态的情况下,最好使用具名对象。
⭕匿名对象的作用
匿名对象在某些情况下非常有用,特别是在需要执行一系列临时操作时。由于匿名对象没有被命名,也无法再次访问,因此它们通常被用于临时操作,而不是保存数据。匿名对象的生命周期仅限于所在的表达式,一旦表达式结束,匿名对象将被销毁。
匿名对象还可以用于链式调用方法。这种用法允许在一行代码中依次调用多个成员函数,并对同一个对象进行串联操作。
MyClass().doSomething().doSomethingElse().processData();
在面的代码中,每个成员函数都返回一个新的临时对象,这样就可以在一行代码中依次调用多个成员函数。
由于匿名对象没有被命名,无法在其作用域之外引用它。因此,如果需要在多个地方使用相同的对象或需要保留对象的状态,最好创建一个具名对象来替代匿名对象。
六、总结
在上面我们介绍了:初始化列表、静态成员、友元、内部类和匿名对象,并且博主提供了对这些概念的全面介绍,帮助您理解和应用它们在C++编程中的作用和用法。
在初始化列表部分,我们了解了初始化列表的概念和优点,包括提高效率和简化代码。我们还学习了explicit关键字的作用和使用场景。
在静态成员部分,我们探讨了静态数据成员和静态函数成员的概念,并阐述了使用静态成员的优点,如共享数据、全局访问和一致性。我们还提到了使用静态成员时需要注意的事项。
在友元部分,我们理解了友元的概念,并学习了类友元和函数友元的用法。我们明白了友元允许其他类或函数访问私有成员的能力,同时强调了谨慎使用友元的重要性。
在内部类部分,我们了解了内部类的概念和特点。我们知道内部类是在另一个类的内部定义的类,并且具有访问外部类的成员的能力。
最后,在匿名对象部分,我们学习了匿名对象的概念和作用。我们了解到匿名对象通常用于一次性操作或作为函数调用的参数,但它们的生命周期仅限于所在的表达式。
温馨提示
感谢您对博主文章的关注与支持!在阅读本篇文章的同时,我们想提醒您留下您宝贵的意见和反馈。如果您喜欢这篇文章,可以点赞、评论和分享给您的同学,这将对我提供巨大的鼓励和支持。另外,我计划在未来的更新中持续探讨与本文相关的内容。我会为您带来更多关于C++以及编程技术问题的深入解析、应用案例和趣味玩法等。请继续关注博主的更新,不要错过任何精彩内容!
再次感谢您的支持和关注。我们期待与您建立更紧密的互动,共同探索C++、算法和编程的奥秘。祝您生活愉快,排便顺畅!