💬 欢迎讨论:如果你在学习过程中有任何问题或想法,欢迎在评论区留言,我们一起交流学习。你的支持是我继续创作的动力! 👍 点赞、收藏与分享:觉得这篇文章对你有帮助吗?别忘了点赞、收藏并分享给更多的小伙伴哦!你们的支持是我不断进步的动力! 🚀 分享给更多人:如果你觉得这篇文章对你有帮助,欢迎分享给更多对C++感兴趣的朋友,让我们一起进步!
初始化列表 是构造函数中用于初始化类成员变量的一种特殊机制。与在构造函数体中直接赋值不同,初始化列表可以提高效率,尤其是对于某些特定类型的成员变量,它是唯一可行的初始化方式。
在C++中,我们有两种主要方式来初始化类的成员变量:
class MyClass {
public:
MyClass(int x) {
this->_x = x;// 在构造函数体内赋值
//_x=x;
}
private:
int _x;
};初始化列表赋值:在构造函数的初始化列表中直接对成员变量进行初始化。
例如:
class A {
public:
A(int x) : _x(x) { // 使用初始化列表赋值
}
private:
int _x;
};内置类型(如 int):对于内置类型,使用初始化列表和在构造函数体内赋值在效率上几乎没有差别。内置类型没有构造函数,也不会进行隐式初始化(即它们是随机值)。构造函数体内赋值或初始化列表赋值都只进行一次操作。因此,选择哪种方式主要是基于代码的清晰性和一致性。
类类型:对于类类型的成员变量,如果没有使用初始化列表,成员变量会先调用默认构造函数进行初始化,然后在构造函数体内再赋值。这样就相当于进行了两次操作:一次初始化,一次赋值。而使用初始化列表时,成员变量只会被初始化一次,效率更高。
const)、引用类型 (reference) 或没有默认构造函数的对象,必须通过初始化列表进行初始化,否则编译器会报错。
const、引用类型,或没有默认构造函数的类成员,必须通过初始化列表进行初始化,否则编译器无法自动处理这些成员的初始化。class Time {
public:
Time(int hour) : _hour(hour) {
cout << "Time() called" << endl;
}
private:
int _hour;
};在这个例子中,Time 类的构造函数使用了初始化列表,将传入的参数 hour 直接赋值给成员变量 _hour。这样,_hour 在对象构造时就被初始化,而不需要在构造函数体内赋值。
语法结构:初始化列表的使用方式是在构造函数名后跟一个冒号,接着是一个以逗号分隔的成员变量列表,每个成员变量后面紧跟括号中的初始值或表达式。
基本语法格式:
ClassName(参数列表) : 成员变量1(初始值), 成员变量2(初始值), ... { // 构造函数体 }
class Myclass
{
public:
Myclass(int x,int y):_x=x,_y=y{//使用初始化列表初始化成员变量}
private:
int _x;
int _y;
};引用类型和常量类型等,只能通过初始化列表进行初始化。
引用类型成员变量在 C++ 中必须在声明时被初始化,不能在构造函数体内赋值,必须使用初始化列表。
class MyClass {
public:
MyClass(int& ref) : _ref(ref) {
// _ref 是引用类型,必须在初始化列表中初始化
}
private:
int& _ref;
};class MyClass { public: MyClass(int n) : _n(n) { // _n 是 const 类型,必须在初始化列表中初始化 } private: const int _n; };
class Time {
public:
Time(int hour) : _hour(hour) {}
private:
int _hour;
};
class Date {
public:
Date(int year, int month, int day) : _year(year), _month(month), _day(day), _t(12) {
// _t 是 Time 类型,必须在初始化列表中调用 Time 的构造函数
}
private:
int _year;
int _month;
int _day;
Time _t; // Time 没有默认构造函数
};C++11 引入了成员变量默认值的概念。可以在类的声明中为成员变量提供默认值,这些默认值将在没有通过初始化列表显式初始化时使用。
class MyClass { public: MyClass() : _b(2) { // _a 使用默认值1 // 构造函数体 } private: int _a = 1; // 默认值 int _b; };
尽管初始化列表中的成员可以按任何顺序出现,但成员变量的初始化顺序是按照它们在类中声明的顺序进行的,而不是它们在初始化列表中的顺序。
class MyClass { public: MyClass(int a, int b) : _b(b), _a(a) { // 尽管 _b 在初始化列表中先出现,但 _a 会首先被初始化 } private: int _a; int _b; };
建议:为了保持代码的一致性和可读性,初始化列表的顺序成员变量声明顺序一致。
1. 每个构造函数都有初始化列表,即使你没有显式地写出它。 2. 每个成员变量都必须被初始化,即使它没有在初始化列表中显式地被初始化。 3. 对于引用类型、常量和没有默认构造函数的类类型成员,必须在初始化列表中进行初始化。 4. C++11 允许在成员变量声明时提供默认值,这些默认值会在初始化列表中未显式初始化时使用。 5. 初始化顺序取决于成员变量在类中的声明顺序,而不是它们在初始化列表中的顺序。

在C++中,类型转换(Type Conversion)是指将一种数据类型转换为另一种数据类型的过程。对于类而言,C++允许将内置类型或类类型转换为其他类类型,这一功能在面向对象编程中非常有用。类型转换可以是显式的(explicit)或隐式的(implicit),并且它们涉及构造函数、转换运算符和explicit关键字。
C++支持将内置类型(如
int、double等)隐式地转换为自定义的类类型。这是通过定义带有内置类型参数的构造函数来实现的。
在没有explicit关键字修饰构造函数的情况下,编译器会自动将符合构造函数参数类型的内置类型值隐式转换为类对象。
示例:
class A {
public:
A(int a1) : _a1(a1) {}
void Print() {
cout << _a1 << endl;
}
private:
int _a1;
};
int main() {
A obj = 10; // 隐式将 int 10 转换为 A 类型对象
obj.Print(); // 输出: 10
}explicit 防止隐式转换有时候,隐式类型转换会引发意想不到的错误或逻辑问题。为了防止这些错误,C++允许我们使用explicit关键字修饰构造函数,这样可以禁止该构造函数参与隐式转换。
示例:
class A {
public:
explicit A(int a1) : _a1(a1) {}
void Print() {
cout << _a1 << endl;
}
private:
int _a1;
};
int main() {
// A obj = 10; // 错误:explicit 阻止了隐式转换
A obj(10); // 正确:必须显式调用构造函数
obj.Print(); // 输出: 10
}在这个例子中,explicit关键字阻止了A obj = 10;的隐式类型转换,必须使用A obj(10);进行显式调用构造函数来创建对象。这种方式避免了潜在的类型转换混淆问题。
C++也允许将一个类类型的对象隐式转换为另一个类类型。这通常通过类的构造函数来实现。例如,一个类可以通过接受另一个类类型对象的构造函数进行隐式转换。
在下面的例子中,B类通过构造函数接受一个A类对象,这样当我们将A类对象赋值给B类时,C++会自动进行隐式转换。
示例:
class A {
public:
A(int a1) : _a1(a1) {}
int Get() const {
return _a1;
}
private:
int _a1;
};
class B {
public:
B(const A& a) : _b(a.Get()) {}
void Print() {
cout << _b << endl;
}
private:
int _b;
};
int main() {
A objA(10);
B objB = objA; // A 类型对象隐式转换为 B 类型对象
objB.Print(); // 输出: 10
}与内置类型的隐式转换类似,我们也可以使用explicit关键字来防止类类型之间的隐式转换。如下所示:
class B {
public:
explicit B(const A& a) : _b(a.Get()) {}
void Print() {
cout << _b << endl;
}
private:
int _b;
};
int main() {
A objA(10);
// B objB = objA; // 错误:explicit 阻止了隐式转换
B objB(objA); // 正确:显式调用构造函数
objB.Print(); // 输出: 10
}为了更好地理解类型转换,下面我们结合一个稍复杂的例子来展示如何利用类型转换优化代码中的对象构造和赋值操作。
#include<iostream>
using namespace std;
class A {
public:
// 构造函数,支持隐式类型转换
A(int a1) : _a1(a1) {}
A(int a1, int a2) : _a1(a1), _a2(a2) {}
void Print() {
cout << _a1 << " " << _a2 << endl;
}
int Get() const {
return _a1 + _a2;
}
private:
int _a1 = 1;
int _a2 = 2;
};
class B {
public:
// 接受 A 类型的对象,进行隐式类型转换
B(const A& a) : _b(a.Get()) {}
private:
int _b = 0;
};
int main() {
A aa1 = 1; // 隐式将 int 1 转换为 A 对象
aa1.Print(); // 输出: 1 2
const A& aa2 = 1; // 隐式将 int 1 转换为 A 对象,并绑定到常量引用
A aa3 = {2, 2}; // 使用 C++11 的列表初始化语法
aa3.Print(); // 输出: 2 2
B b = aa3; // A 对象隐式转换为 B 对象
const B& rb = aa3; // 隐式转换时绑定常量引用
return 0;
}通过这种类型转换机制,C++允许我们使用内置类型或其他类类型轻松构造对象,并在对象之间传递数据。
在C++11之前,类型转换通常只能支持单参数的构造函数。但从C++11开始,C++标准引入了列表初始化(也称为统一初始化),使得我们能够更灵活地传递多个参数来进行类型转换。
例如:
A aa3 = {2, 2}; // 使用列表初始化,传递两个参数
在这个例子中,{2, 2} 是一个初始化列表,C++11允许我们通过这种方式为类的构造函数传递多个参数。这种形式的类型转换比传统的单参数转换更加灵活,可以处理更复杂的初始化场景。
在对象的构造过程中,如果出现了临时对象的构造和拷贝构造的连续过程,编译器通常会进行优化。特别是在C++11中,编译器可以省略临时对象的构造和拷贝,直接构造最终对象。
示例:上述的代码其实是这样的:
A aa3 = A(2, 2); //然后编译器优化为直接构造 aa3,而不是先构造临时对象再拷贝
未优化前:编译器遇到A(2, 2)的构造操作后,通常会先构造一个临时对象,再通过拷贝构造函数将其赋值给aa3。然而,现代编译器通常会进行优化,直接将aa3构造出来,而不需要先构造临时对象。这种优化被称为返回值优化(RVO)或拷贝省略。
C++中的类型转换是一个强大且灵活的机制,通过构造函数,我们可以轻松地在内置类型和类类型之间进行隐式或显式的转换。同时,explicit关键字让我们能够严格控制类型转换,避免不必要的隐式转换带来的潜在问题。C++11的引入进一步增强了类型转换的灵活性,特别是列表初始化使得我们能够传递多个参数进行转换。理解并合理使用这些机制,可以让我们编写出更加简洁、灵活的代码。
static 成员详解——静态成员变量与静态成员函数 在C++中,static成员既可以用于修饰类的变量,也可以用于修饰类的函数。通过static,我们可以实现成员共享、函数无依赖等功能,特别适用于一些类级别的操作,而不依赖于具体的对象实例。静态成员具有一些特殊的属性和行为。
static成员变量,称为静态成员变量,它是类的所有对象共享的变量,而不是每个对象独立拥有的。静态成员变量存储在静态存储区(也称为全局区),并且只能在类外初始化。
示例:通过静态成员变量计算类对象的数量。
#include<iostream>
using namespace std;
class A {
public:
A() {
++_scount; // 每创建一个对象,计数加1
}
A(const A& t) {
++_scount; // 每调用拷贝构造函数,计数加1
}
~A() {
--_scount; // 每销毁一个对象,计数减1
}
static int GetACount() {
return _scount; // 返回当前对象的数量
}
private:
// 声明静态成员变量
static int _scount;//声明静态变量不能在此位置(声明位置)给缺省值
};
// 类外初始化静态成员变量
int A::_scount = 0;
int main() {
cout << "初始对象数量: " << A::GetACount() << endl; // 初始对象数量为 0
A a1, a2; // 创建两个对象,计数加2
A a3(a1); // 拷贝构造,计数加1
cout << "当前对象数量: " << A::GetACount() << endl; // 输出 3
return 0;
}输出:
初始对象数量: 0 当前对象数量: 3
解释:
1. _scount 是静态成员变量,它被所有 A 类对象共享。无论创建多少个对象,所有对象共享这一个计数器。 2. 静态成员变量在类外进行初始化:int A::_scount = 0;,这是强制要求的,不能在类内部直接赋值。 3. 通过类名 A::GetACount() 或对象 a1.GetACount() 来访问静态成员函数 GetACount(),输出当前对象的数量。
静态成员函数是类的成员函数,但是它与普通的成员函数不同,它不依赖于具体的对象实例,可以通过类名直接调用。静态成员函数没有
this指针,因此它只能访问类的静态成员变量或静态成员函数,不能访问非静态成员。
3.2.1 静态成员函数的特性
this指针:静态成员函数没有 this 指针,因此不能访问非静态成员。示例:静态成员函数的使用。
#include<iostream>
using namespace std;
class A {
public:
A() {
++_scount;
}
A(const A& t) {
++_scount;
}
~A() {
--_scount;
}
static int GetACount() {
return _scount; // 静态成员函数访问静态成员变量
}
private:
static int _scount;
};
int A::_scount = 0;
int main() {
A a1, a2;
cout << "当前对象数量: " << A::GetACount() << endl; // 通过类名访问静态成员函数
return 0;
}输出:
当前对象数量: 2解释:
GetACount() 可以通过类名 A::GetACount() 调用,而不依赖于具体的对象。this 指针,因此它不能访问非静态成员变量或函数。静态成员既可以通过类名来访问,也可以通过对象来访问。
静态成员不属于某个具体的对象,而是属于整个类,因此它们可以通过类名来访问。例如,A::GetACount() 是通过类 A 的名字直接访问静态成员函数 GetACount()。
虽然静态成员不属于对象,但仍然可以通过对象来访问静态成员。例如,a1.GetACount() 也可以调用静态成员函数,尽管底层实现实际上仍然是通过类来访问的。
示例:
int main() { A a1, a2; cout << A::GetACount() << endl; // 通过类名访问 cout << a1.GetACount() << endl; // 通过对象访问 return 0; }
静态成员变量不能在类内初始化,必须在类外进行初始化。这是因为静态成员变量存储在静态存储区中,它们不属于某个对象实例,因此不能在类的构造函数或初始化列表中进行初始化。
示例:
class A { public: static int _count; }; // 类外初始化 int A::_count = 0;
在上面的例子中,A::_count 在类外初始化为 0,所有对象共享这个静态变量。
静态成员与普通成员一样,也受访问控制修饰符(
public、protected、private)的限制。即使静态成员属于类,而不是对象,但它们仍然需要遵守访问控制规则。
public 静态成员可以被类的任何对象或函数访问,包括类外代码。
class A {
public:
static int _public_count;
};
int A::_public_count = 0;
int main() {
A::_public_count = 10; // 可以在类外访问
cout << A::_public_count << endl;
return 0;
}private 静态成员private 静态成员只能被类的成员函数访问,不能被类外代码直接访问。
class A {
private:
static int _private_count;
public:
static int GetPrivateCount() {
return _private_count; // 只能通过成员函数访问
}
};
int A::_private_count = 0;
int main() {
// cout << A::_private_count; // 错误,无法访问 private 静态成员
cout << A::GetPrivateCount() << endl; // 通过静态成员函数访问
return 0;
}static 成员的实际应用静态成员通常用于实现某些类级别的操作,例如计算对象的数量、跟踪全局状态等。通过静态成员,我们可以方便地在类内部管理全局信息,而无需创建对象实例。
题目代码
class Sum {
public:
Sum() {
_ret += _i; // 每次调用构造函数时,将当前 i 的值加到 _ret 中
++_i; // 自增 _i
}
static int GetRet() {
return _ret; // 返回累加结果
}
private:
static int _i; // 用于计数的静态变量
static int _ret; // 用于存储结果的静态变量
};
// 初始化静态成员变量
int Sum::_i = 1;
int Sum::_ret = 0;
class Solution {
public:
int Sum_Solution(int n) {
Sum arr[n]; // 创建 n 个 Sum 对象,触发构造函数进行累加
return Sum::GetRet(); // 返回累加的结果
}
};在C++中,构造函数和析构函数的调用顺序遵循一定的规则,尤其是在全局变量和静态对象的情况下,了解它们的调用顺序非常重要。
设已经有 A、B、C、D 四个类的定义,程序中涉及构造函数和析构函数的调用顺序,解析如下。
class A { public: A() { cout << "A Constructor" << endl; } ~A() { cout << "A Destructor" << endl; } }; class B { public: B() { cout << "B Constructor" << endl; } ~B() { cout << "B Destructor" << endl; } }; class C { public: C() { cout << "C Constructor" << endl; } ~C() { cout << "C Destructor" << endl; } }; class D { public: D() { cout << "D Constructor" << endl; } ~D() { cout << "D Destructor" << endl; } }; C c; // 全局变量 int main() { A a; B b; static D d; // 静态局部变量 return 0; }
构造函数的调用顺序
构造函数的调用顺序是先全局变量,再局部变量,最后静态局部变量。在上面的代码中:
C 是全局变量,因此它的构造函数 C() 在 main 函数执行之前被调用。A 和 B 是局部变量,它们的构造函数按照声明的顺序,在 main 函数中依次调用。D 是静态局部变量,它的构造函数在 main 函数的执行中调用,但只会在程序的第一次运行时调用一次。构造函数的调用顺序为:
C Constructor // 全局变量,最先调用 A Constructor // 局部变量,按声明顺序 B Constructor // 局部变量,按声明顺序 D Constructor // 静态局部变量,最后调用
析构函数的调用顺序:
main 函数执行结束后调用。析构函数的调用顺序为
B Destructor // 局部变量,先析构,顺序与构造相反 A Destructor // 局部变量,顺序与构造相反 D Destructor // 静态局部变量,函数结束后调用 C Destructor // 全局变量,最后析构
总结
3.7static成员函数与变量总结
在C++中,static成员为类提供了管理全局数据和类级别操作的强大机制。静态成员变量被所有对象共享,存储在静态存储区中,而静态成员函数则可以在没有对象的情况下通过类名直接调用。静态成员与普通成员一样,受访问控制修饰符的限制,可以是public、private或protected。同时,静态成员变量不能在类内初始化,必须在类外进行初始化。通过静态成员,我们可以方便地实现对象计数、全局状态管理等功能,这让类在不依赖对象实例的情况下,依然能够提供有用的功能。

相信通过这篇文章你对C++类与对象高级部分的有了初步的了解。如果此篇文章对你学习C++有帮助,期待你的三连,你的支持就是我创作的动力!!!