1.构造函数:
构造函数是用于初始化对象的特殊成员函数。当创建一个对象时,构造函数会被自动调用。构造函数有以下几个特点:
- 名称相同于类名:构造函数的名称必须与类名完全相同。
- 没有返回值:构造函数没有返回类型,也不返回任何值,甚至不能定义为
void
。 - 可以有参数:构造函数可以接受参数,用于设置对象的初始状态。
构造函数的类型
-
默认构造函数:
- 这是一个没有参数的构造函数。即使不定义默认构造函数,编译器也会生成一个。
class Person { public: Person() { name = "Unnamed"; age = 0; } private: std::string name; int age; };
用法:
Person p; // 调用默认构造函数
参数化构造函数:
- 带参数的构造函数允许用户在创建对象时提供初始值。
class Person { public: Person(const std::string& name, int age) { this->name = name; this->age = age; } private: std::string name; int age; };
用法:
Person p("Alice", 30); // 调用参数化构造函数
拷贝构造函数:
- 拷贝构造函数用于复制对象的值。它的参数通常是同类型对象的引用。
class Person { public: Person(const Person &other) { name = other.name; age = other.age; } private: std::string name; int age; };
用法:
Person p1("Alice", 30); Person p2 = p1; // 调用拷贝构造函数
委托构造函数(C++11及以后):
- 一个构造函数可以调用另一个构造函数,从而减少重复代码。
class Person { public: Person() : Person("Unnamed", 0) {} // 默认构造函数委托 Person(const std::string& name, int age) { this->name = name; this->age = age; } private: std::string name; int age; };
使用示例
我们来看看一个完整的示例,包含不同类型的构造函数:
#include <iostream> #include <string> class Person { public: // 默认构造函数 Person() : Person("Unnamed", 0) { } // 委托到参数化构造函数 // 参数化构造函数 Person(const std::string& name, int age) { this->name = name; this->age = age; } // 拷贝构造函数 Person(const Person& other) { name = other.name; age = other.age; } // 打印信息的方法 void display() const { std::cout << "Name: " << name << ", Age: " << age << std::endl; } private: std::string name; int age; }; int main() { Person p1; // 调用默认构造函数 Person p2("Alice", 30); // 调用参数化构造函数 Person p3 = p2; // 调用拷贝构造函数 p1.display(); // 输出: Name: Unnamed, Age: 0 p2.display(); // 输出: Name: Alice, Age: 30 p3.display(); // 输出: Name: Alice, Age: 30 return 0; }
在这个示例中,
Person
类定义了三种构造函数: - 默认构造函数,将名称设置为 "Unnamed" 和年龄设置为 0。
- 参数化构造函数,允许用户提供名称和年龄。
- 拷贝构造函数,可以复制已有对象的属性。
- 这是一个没有参数的构造函数。即使不定义默认构造函数,编译器也会生成一个。
主要注意
-
构造函数的调用顺序:对于一个类中的所有构造函数,可以根据需要来重载以及利用委托。多层次类继承时,基类部分在派生类构造函数调用之前被构造。
-
initializer list:可以在构造函数的初始化列表中直接初始化成员变量,这对于 const 成员变量和引用非常重要。
-
自定义的拷贝构造函数:当类中有动态分配的资源(如指针),则必须自定义拷贝构造函数和赋值运算符,以防止多次释放相同内存导致的未定义行为。
2类型转换
在C++中,类型转换是将一种数据类型的值转换为另一种数据类型的过程。这在处理不同类型的变量和对象时非常常见。C++提供了多种类型转换的方式,主要包括隐式转换、显式转换和类型转换运算符。以下是对这些不同类型转换的详细讲解。
1. 隐式转换(Implicit Conversion)
隐式转换是自动进行的类型转换,通常由编译器在某些条件下自动完成。例如,当进行算术运算时,较小精度的类型(如 int
)会自动转换为较高精度的类型(如 double
)。
int a = 10;
double b = a; // 隐式转换,int 转换为 double
std::cout << b; // 输出: 10.0
2. 显式转换(Explicit Conversion)
显式转换需要使用强制类型转换来明确地要求编译器进行类型转换。C++提供了几种方式进行显式转换。
a. C 风格的强制类型转换
使用括号来强制转换,例如 (new_type)expression
。
double a = 9.8;
int b = (int)a; // 强制转换,double 转换为 int
std::cout << b; // 输出: 9
b. static_cast
static_cast
是推荐用于大多数类型转换的方式,适用于基本类型之间的转换、类层次中的上下转换等。
double a = 9.8;
int b = static_cast<int>(a); // 使用 static_cast 进行转换
std::cout << b; // 输出: 9
c. dynamic_cast
dynamic_cast
主要用于类的多态转换,用于安全地将基类指针或引用转换为派生类指针。当转换失败时,返回 nullptr
(对于指针)或抛出异常(对于引用)。
class Base { virtual void func() {} }; // 需要至少一个虚函数
class Derived : public Base {};
Base* b = new Derived();
Derived* d = dynamic_cast<Derived*>(b); // 安全地转换
if (d != nullptr) {
std::cout << "转换成功!" << std::endl;
} else {
std::cout << "转换失败!" << std::endl;
}
d. const_cast
const_cast
用于添加或去除 const
或 volatile
属性。例如:
const int a = 10;
int* b = const_cast<int*>(&a); // 去除 const 属性
e. reinterpret_cast
reinterpret_cast
是最低级别的转换,允许将指针类型转换为任何其他指针类型。这个操作不安全,需要谨慎使用。
int a = 10;
void* p = reinterpret_cast<void*>(&a); // 将 int* 转换为 void*
int* q = reinterpret_cast<int*>(p); // 再将 void* 转换回 int*
std::cout << *q; // 输出: 10
3. 类型转换运算符(Type Conversion Operator)
自定义类型也可以定义转换运算符,允许对象在需要时自动转换为其他类型。
class Fraction {
public:
Fraction(int numerator, int denominator) : num(numerator), den(denominator) {}
operator double() const { return static_cast<double>(num) / den; } // 定义转换运算符
private:
int num;
int den;
};
int main() {
Fraction f(1, 2);
double d = f; // 隐式调用转换运算符
std::cout << d; // 输出: 0.5
return 0;
}
类型转换在C++中是一项重要的特性,适用于实现灵活的代码设计。了解不同的类型转换方法及其使用场景有助于编写出更清晰和安全的代码。一般来说,推荐使用 static_cast
进行静态类型转换,因为它更安全且易于读懂;针对具体应用,合理选择其他类型转换是必要的。