在理解了类的基础上,我们现在深入探讨C++对象的创建、初始化和使用。对于初学者来说,掌握这些步骤是理解面向对象编程(OOP)的关键。
1. 对象的创建
在C++中,对象的创建是通过类名后跟一对圆括号来实现的。这个过程称为实例化,即根据类模板创建一个具体的对象。下面是一个简单的例子:
#include <iostream>
#include <string>
class Person {
public:
std::string name;
int age;
// 构造函数
Person(std::string n, int a) : name(n), age(a) {}
// 显示信息的方法
void display() {
std::cout << "Name: " << name << ", Age: " << age << std::endl;
}
};
int main() {
// 创建对象
Person person1("Alice", 30);
Person person2("Bob", 25);
// 使用对象的方法
person1.display();
person2.display();
return 0;
}
在这个例子中,Person类有两个数据成员:name和age,以及一个构造函数Person(std::string n, int a)用于初始化这两个成员。在main函数中,我们创建了两个Person对象:person1和person2,并通过调用它们的display方法来显示信息。
2. 对象的初始化
对象的初始化通常是通过构造函数来完成的。构造函数是一种特殊的成员函数,它在对象创建时自动调用。构造函数的名字与类名相同,且没有返回类型。
2.1 默认构造函数
如果一个类没有定义任何构造函数,编译器会自动生成一个默认构造函数。默认构造函数不接受任何参数,并且不执行任何初始化操作(对于基本数据类型,成员变量会被初始化为不确定的值)。
class MyClass {
public:
int value;
};
int main() {
MyClass obj; // 调用默认构造函数
// obj.value 的值是未定义的
return 0;
}
为了避免未定义行为,我们通常建议为类提供自己的构造函数,即使是一个简单的默认初始化。
2.2 带参数的构造函数
在前面的例子中,我们已经展示了如何定义一个带参数的构造函数。通过参数列表,我们可以在对象创建时初始化其成员变量。
class Point {
public:
int x, y;
// 带参数的构造函数
Point(int a, int b) : x(a), y(b) {}
};
int main() {
Point p(10, 20); // 调用带参数的构造函数
// p.x = 10, p.y = 20
return 0;
}
2.3 初始化列表
在构造函数中,我们可以使用初始化列表来初始化成员变量。初始化列表位于构造函数参数列表之后,成员变量初始化之前,由冒号:开始。
class Rectangle {
public:
int width, height;
// 使用初始化列表
Rectangle(int w, int h) : width(w), height(h) {}
};
int main() {
Rectangle rect(30, 40); // 调用构造函数并使用初始化列表
// rect.width = 30, rect.height = 40
return 0;
}
使用初始化列表通常比在构造函数体内赋值更高效,特别是对于常量成员变量和引用成员变量,它们必须在初始化列表中初始化。
class Rectangle {
public:
int width, height;
// 使用初始化列表
Rectangle(int w, int h) : width(w), height(h) {}
};
int main() {
Rectangle rect(30, 40); // 调用构造函数并使用初始化列表
// rect.width = 30, rect.height = 40
return 0;
}
2.4 拷贝构造函数
拷贝构造函数是一个特殊的构造函数,它接受一个同类型的对象作为参数,并用于创建一个新的对象作为该对象的副本。
class StringCopy {
public:
char* str;
// 普通构造函数
StringCopy(const char* s) {
str = new char[strlen(s) + 1];
strcpy(str, s);
}
// 拷贝构造函数
StringCopy(const StringCopy& other) {
str = new char[strlen(other.str) + 1];
strcpy(str, other.str);
}
// 析构函数
~StringCopy() {
delete[] str;
}
};
int main() {
StringCopy str1("Hello");
StringCopy str2 = str1; // 调用拷贝构造函数
// 现在str1和str2都指向包含"Hello"的字符串
return 0;
}
在上面的例子中,StringCopy类有一个普通构造函数和一个拷贝构造函数。当我们尝试用一个已存在的StringCopy对象来初始化另一个StringCopy对象时(如StringCopy str2 = str1;),拷贝构造函数会被调用。
3. 对象的使用
创建了对象并初始化后,我们就可以通过点操作符.来访问对象的成员变量和成员函数了。
class Car {
public:
std::string make;
std::string model;
int year;
void drive() {
std::cout << make << " " << model << " (" << year << ") is driving." << std::endl;
}
};
int main() {
Car myCar;
myCar.make = "Toyota";
myCar.model = "Camry";
myCar.year = 2020;
myCar.drive(); // 输出: Toyota Camry (2020) is driving.
return 0;
}
在这个例子中,我们创建了一个Car对象myCar,并通过点操作符设置了其成员变量make、model和year的值。然后,我们调用了myCar的drive方法来显示一条消息。
4. 对象的销毁
对象的生命周期从创建开始,到销毁结束。当对象超出其作用域或显式删除时,它的析构函数会被调用。析构函数是一个特殊的成员函数,它在对象销毁时自动调用,用于执行清理工作,如释放动态分配的内存。
class MyClass {
public:
~MyClass() {
std::cout << "MyClass destructor called." << std::endl;
}
};
int main() {
MyClass obj; // 创建对象时,构造函数被调用(但这里没有显式定义构造函数)
// 当main函数结束时,obj超出作用域,析构函数被调用
return 0;
}
在上面的例子中,MyClass类有一个析构函数,它在对象obj销毁时被调用。输出将是:
MyClass destructor called.
对于动态分配的对象(使用new关键字创建的对象),我们需要使用delete关键字来显式销毁它们,并调用析构函数。
class MyClass {
public:
~MyClass() {
std::cout << "MyClass destructor called." << std::endl;
}
};
int main() {
MyClass* obj = new MyClass(); // 动态创建对象
// ... 使用对象 ...
delete obj; // 显式销毁对象,调用析构函数
return 0;
}
在这个例子中,我们动态创建了一个MyClass对象obj,并在使用完毕后使用delete关键字来销毁它。输出同样将是:
MyClass destructor called.