构造函数(进阶)
1.1构造函数体赋值
#include <iostream>
class MyClass {
private:
int myInt;
double myDouble;
std::string myString;
public:
MyClass(int i, double d, const std::string& s) : myInt(i), myDouble(d), myString(s) {
// 在构造函数体内部可以进行其他操作
std::cout << "Constructor called." << std::endl;
}
void printData() {
std::cout << "myInt: " << myInt << std::endl;
std::cout << "myDouble: " << myDouble << std::endl;
std::cout << "myString: " << myString << std::endl;
}
};
int main() {
MyClass obj(10, 3.14, "Hello");
obj.printData();
return 0;
}
在这个例子中,我们定义了一个名为 MyClass
的类,它有三个私有成员变量 myInt
、myDouble
和 myString
。
在构造函数声明中,我们使用成员初始化列表来初始化这些成员变量。在构造函数的参数列表后面使用冒号,并在冒号后面逐个列出成员变量名,然后用括号括起来的值或表达式进行赋值。
在示例代码中,构造函数的参数是一个整数 i
、一个双精度浮点数 d
和一个字符串引用 s
。我们使用成员初始化列表 myInt(i), myDouble(d), myString(s)
来初始化相应的成员变量。
然后在构造函数体内部,我们可以进行其他操作。在示例中,我们在构造函数中打印一条消息。
最后,我们在 main
函数中创建一个 MyClass
的对象 obj
,并将参数传递给构造函数进行初始化。然后调用 printData()
方法来打印成员变量的值。
这样,使用成员初始化列表,我们就可以在构造函数中给成员变量赋值。
1.2初始化列表
初始化列表是用于在构造函数中初始化类成员变量的一种语法构造。它的基本形式是在构造函数头部的参数列表后使用冒号(:)来引出初始化列表,然后以逗号分隔的形式对每个成员变量进行初始化赋值。
语法形式如下:
ClassName::ClassName(parameter list)
: member1(value1), member2(value2), ..., memberN(valueN)
{
// 构造函数体
}
在上述形式中,ClassName
是类的名字,parameter list
是构造函数的参数列表。冒号(:)之后是初始化列表,接着是构造函数体。
在初始化列表中,我们通过成员变量名和初始化值来初始化每个成员变量。值可以是常量、变量、表达式或其他初始化方法。
初始化列表的优点是可以直接初始化成员变量,而不是采用在构造函数体内使用赋值语句的方式。这样可以提高效率并使代码更简洁。
【注意】
在使用初始化列表时,有一些注意事项需要记住:
(1). 初始化顺序:初始化列表中的成员变量的初始化顺序与它们在类中声明的顺序一致,而不是与它们在初始化列表中出现的顺序一致。因此,确保成员变量的初始化顺序与其声明顺序一致是很重要的。
(2). 引用类型初始化:如果类成员变量是引用类型,那么在初始化列表中必须使用初始化列表来为其赋值,因为引用必须在构造函数体之前初始化。在构造函数体内部为引用类型赋值是无效的。
(3). 常量成员变量初始化:如果类有常量成员变量,那么它们必须在初始化列表中进行初始化,因为常量成员变量一旦被声明,就无法在构造函数体内进行赋值。
(4). 成员对象初始化:如果类的成员变量是对象(而不是基本数据类型或指针类型),那么它们也可以在初始化列表中进行初始化。对于这些成员对象,会调用它们自己的构造函数来初始化。
(5). 使用表达式和函数调用:初始化列表中可以使用表达式和函数调用,以便计算初始化值。这使得可以在构造函数开始之前执行复杂的计算或调用其他函数。
(6). 慎用默认参数:构造函数的参数如果有默认值,当使用初始化列表时,不会触发默认参数的调用。因此,在使用初始化列表时,如果想使用默认值,需要手动提供参数值。
注意事项的遵守可以确保正确使用初始化列表,并正确初始化类的成员变量。
1.3explicit 关键字
explicit
关键字是在 C++ 中用于修饰单参数构造函数的关键字。它的作用是防止编译器执行隐式类型转换。当一个构造函数被声明为 explicit
时,它只能用于显式地创建对象,不能用于隐式的类型转换。
下面是一个示例来演示 explicit
关键字的使用:
class MyClass {
private:
int myInt;
public:
explicit MyClass(int i) : myInt(i) {
// 构造函数体
}
int getInt() {
return myInt;
}
};
int main() {
MyClass obj1(10); // 使用显式构造
int x = obj1.getInt(); // 正常访问成员函数
// MyClass obj2 = 20; (错误) 隐式类型转换不允许
MyClass obj2 = MyClass(20); // 使用显式构造
return 0;
}
在上述示例中,我们定义了 MyClass
类,它有一个私有成员变量 myInt
。
构造函数 MyClass(int i)
被标记为 explicit
,这意味着它只能被显式调用,而不能用于隐式的类型转换。当我们在 main
函数中使用显式构造函数时,例如 MyClass obj1(10)
,这是允许的。
然而,当我们使用隐式类型转换时,例如 MyClass obj2 = 20
,编译器会报错。因为构造函数被声明为 explicit
,它不能用于隐式的类型转换。如果我们需要使用隐式类型转换来创建对象,我们需要手动使用显式构造函数 MyClass(20)
。
通过使用 explicit
关键字,我们可以明确指定构造函数的使用方式,避免意外的隐式类型转换,并增加代码的可读性和安全性。
static成员
在C++中,static
关键字可用于声明静态成员。静态成员是与类本身相关联的成员,而不是与类的每个对象实例相关联的。这意味着无论创建多少个类的对象,静态成员只有一个副本,并且在内存中只有一个实例。
以下是一些关于静态成员的重要事项和特性:
- 静态数据成员:静态数据成员是属于整个类而不是任何类对象的成员。它们在类内部声明,但在类外部进行定义和初始化。
class MyClass { public: static int myStaticInt; }; // 在类外部进行定义和初始化 int MyClass::myStaticInt = 0;
- 静态成员函数:静态成员函数不依赖于类的对象,因此它们没有隐含的 this 指针。它们可以直接通过类名进行调用,而不需要通过对象进行访问。静态成员函数只能访问静态成员变量和其他静态成员函数。
class MyClass { public: static void myStaticFunction() { // 静态成员函数的实现 } }; // 调用静态成员函数 MyClass::myStaticFunction();
- 静态成员的访问:静态成员可以通过类名和作用域解析运算符(::)直接访问,也可以通过类的对象访问。然而,私有的静态成员只能通过公有的静态成员函数访问
- 共享内存空间:静态成员被所有类对象共享,它们分配在静态存储区,因此在内存中只有一个副本。
-
静态成员的初始化:静态成员只能在类外部进行定义和初始化。这是因为静态成员的内存分配和初始化在编译时进行,而不是在运行时。
友元
友元(Friend)是 C++ 中一种特殊的关系,它允许其他类或函数访问一个类的私有成员。通过声明其他类或函数为友元,可以使其在类的内部访问和操作私有成员,即使这些私有成员在普通情况下是不可访问的。
友元关系的关键点如下:
1.友元类:通过在类的声明中使用 friend
关键字,可以将其他类声明为友元类,从而使其能够访问被声明为私有的成员。友元类的所有成员都可以访问被声明的类的私有和保护成员。
class MyClass {
friend class FriendClass; // FriendClass 是 MyClass 的友元类
private:
int privateMember;
};
class FriendClass {
public:
void accessPrivateMember(MyClass& obj) {
obj.privateMember = 10; // 在 FriendClass 中访问私有成员
}
};
1.友元函数:通过在函数的声明中使用 friend
关键字,可以将该函数声明为友元函数,从而使其能够访问被声明为私有的成员。友元函数不是类的成员函数,但它们具有访问类的私有成员的特权。
class MyClass {
friend void friendFunction(MyClass& obj); // friendFunction 是 MyClass 的友元函数
private:
int privateMember;
};
void friendFunction(MyClass& obj) {
obj.privateMember = 10; // 在 friendFunction 中访问私有成员
}
注意事项:
- 友元关系是单向的。如果将类 A 声明为类 B 的友元,则类 A 可以访问类 B 的私有成员,但类 B 不一定可以访问类 A 的私有成员。
- 友元关系破坏了封装性,因此只有在确实需要访问私有成员并能够保证安全性的情况下,才应该使用友元关系。
- 友元关系建立在类的定义之外,因此友元类和友元函数的声明可以放在类的任何位置,不受类内公有或私有成员的影响。
友元关系在一些特定的场景中非常有用,例如在设计模式中的某些模式中,或者需要在不改变类接口的情况下访问私有成员。然而,过度使用友元关系可能导致代码的依赖关系复杂化,降低了代码的封装性和可维护性,因此应谨慎使用。
内部类
在 C++ 中,内部类是一个类在另一个类的内部进行定义的类。内部类可以访问外部类的私有成员,并且与外部类有不同的访问权限规则。内部类的主要作用是实现更高层次的封装和组织代码。
下面是关于内部类的一些重要事项和特性:
1.内部类的定义:内部类的定义在外部类的内部,并且在外部类的成员函数之外。内部类声明的作用域限定在外部类中,可以在外部类中使用类名加作用域解析运算符(::)来访问内部类。
class OuterClass {
public:
class InnerClass {
// 内部类的成员和函数定义
};
// 外部类的成员和函数定义
};
2.内部类的访问权限:内部类可以直接访问外部类的所有成员,包括私有成员。相反,外部类只能通过创建内部类的对象来访问内部类的成员。这种访问规则可以实现更细粒度的封装。
class OuterClass {
private:
int privateMember;
public:
class InnerClass {
public:
void accessOuterMember(OuterClass& obj) {
obj.privateMember = 10; // 在内部类中访问外部类的私有成员
}
};
};
3.内部类的对象创建:要创建内部类的对象,需要首先创建外部类的对象,然后使用外部类对象来创建内部类的对象。
OuterClass outerObj;
OuterClass::InnerClass innerObj = outerObj.InnerClass(); // 创建内部类的对象
4.内部类的嵌套:内部类也可以定义自己的内部类,从而形成嵌套的类结构。嵌套的内部类可以像访问外部类一样访问上一级的内部类和外部类的成员。
class OuterClass {
public:
class InnerClass1 {
public:
class InnerClass2 {
// 嵌套内部类的成员和函数定义
};
// 内部类1的成员和函数定义
};
// 外部类的成员和函数定义
};