定义:
1. 带 *
的声明:指针类型
-
声明方式:
MyClass* obj;
是一个 指针类型,表示obj
是一个指针,可以指向MyClass
类型的对象。 -
指针特点:
- 指针存储的是对象的地址,可以为空(
nullptr
),也可以重新指向其他对象。 - 指针需要通过
->
运算符来访问对象的成员。 - 指针需要手动管理内存(如果是通过
new
动态分配的)。 - 例如:
MyClass* obj = new MyClass(); // obj 是一个指向 MyClass 的指针 obj->someMethod(); // 通过指针访问方法 delete obj; // 手动释放内存
- 指针存储的是对象的地址,可以为空(
-
适用场景:
- 当你需要动态分配内存时使用指针(比如在堆上分配对象)。
- 当你需要在函数之间传递对象引用而不是对象副本时。
2. 不带 *
的声明:值类型
- 声明方式:
MyClass obj;
是一个 值类型,表示obj
是MyClass
类型的对象,存储在栈上。 - 值类型特点:
- 值类型对象会在栈上分配内存,其生命周期由作用域决定,当作用域结束时,内存会自动释放。
- 通过
.
运算符来访问对象的成员。 - 例如:
MyClass obj; // obj 是一个 MyClass 类型的对象,存储在栈上 obj.someMethod(); // 通过对象访问方法
- 适用场景:
- 当对象的生命周期明确、作用域有限时,使用值类型更简单和安全。
- 避免不必要的动态内存分配,可以提高性能。
3. 引用类型:使用 &
符号
-
引用类型与指针类似,但在语法和使用上有不同的特点。
-
声明方式:
MyClass& objRef = obj;
表示objRef
是一个对obj
的引用。 -
引用特点:
- 引用本质上是对象的别名,必须在定义时初始化,且无法更改其引用的对象。
- 不能为
nullptr
,也没有类似指针的*
或->
运算符。 - 通常用于函数参数或返回值,以避免拷贝对象而是引用现有对象。
- 例如:
MyClass obj; // 正常创建一个 MyClass 对象 MyClass& objRef = obj; // 创建一个对 obj 的引用 objRef.someMethod(); // 通过引用访问方法,与 obj.someMethod() 等价
-
适用场景:
- 当你希望函数参数传递时避免拷贝,但又不希望管理指针时,可以使用引用。
- 作为函数返回值时,可以用于返回一个现有对象的别名。
生命周期的变化:
示例代码:
class MyClass {
public:
MyClass(const std::string& name) : name(name) {
std::cout << "Creat(创建) called for: " << name << std::endl;
}
~MyClass() {
std::cout << "destory(销毁) called for: " << name << std::endl;
}
void greet() const {
std::cout << "Hello, my name is " << name << "!" << std::endl;
}
private:
std::string name;
};
void demonstratePointer() {
MyClass* ptrObj = new MyClass("PointerObject"); // 指针类型
ptrObj->greet(); // 通过指针访问成员
delete ptrObj; // 释放内存
}
void demonstrateValue() {
MyClass valObj("ValueObject"); // 值类型
valObj.greet(); // 通过值对象访问成员
// valObj 的析构函数在函数结束时被自动调用
}
void demonstrateReference() {
MyClass refObj("ReferenceObject"); // 创建值对象
MyClass& ref = refObj; // 引用类型
ref.greet(); // 通过引用访问成员
// refObj 的析构函数在函数结束时被自动调用
}
int main()
{
demonstratePointer(); // 演示指针类型
demonstrateValue(); // 演示值类型
demonstrateReference(); // 演示引用类型
system("pause");
return 0;
}
运行结果如下:
说明:
MyClass
类:
- 包含一个构造函数、析构函数和一个成员函数
greet
。 - 构造函数打印对象的名称,析构函数在对象销毁时打印一条消息。
demonstratePointer
函数(指针类型):
- 创建一个
MyClass
对象的指针ptrObj
,在堆上动态分配内存。 - 使用
->
运算符调用greet
方法,并在最后使用delete
释放内存。 - 对于动态分配的对象,则需要手动释放资源。
demonstrateValue
函数(值类型):
- 创建一个
MyClass
对象valObj
,在栈上分配内存。 - 直接调用
greet
方法,函数结束时自动调用析构函数。 - 对于栈对象,生命周期是自动管理的;
demonstrateReference
函数(引用类型):
- 创建一个
MyClass
对象refObj
,并通过引用ref
引用该对象。 - 通过引用调用
greet
方法,refObj
的析构函数将在函数结束时自动调用。 - 引用类型提供了对象的别名,它的资源释放遵循与值类型相同的规则,都是由绑定的对象来管理。使用引用可以避免不必要的对象拷贝,同时也需要注意引用对象的生命周期。
异常案例:
1.悬空引用
MyClass& createObject() {
MyClass obj("CreatedObject"); // 普通局部对象,不是静态的
return obj; // 返回对局部对象的引用(不安全)
}
int main() {
MyClass& newObj = createObject();
newObj.greet(); // 这里可能会出现未定义行为
return 0;
}
obj
是 createObject
函数中的局部对象。当函数返回时,obj
的生命周期结束并被销毁。然而,函数返回了一个对 obj
的引用,这在 newObj
中保存,但此时 obj
已经不存在了。调用 newObj.greet()
会访问已经被释放的内存,导致 未定义行为,可能会导致程序崩溃或其他不可预期的结果。
改进方案1:可以在堆上创建对象,并返回对象的指针。但这需要调用者在适当的时候手动释放内存。
MyClass* createObject() {
MyClass* obj = new MyClass("CreatedObject"); // 在堆上分配
return obj; // 返回指针
}
int main() {
MyClass* newObj = createObject();
newObj->greet(); // 使用指针访问
delete newObj; // 记得释放内存,避免内存泄漏
return 0;
}
改进方案2:如果 MyClass
是可以被复制的,可以直接返回对象本身。C++ 编译器会优化返回对象的过程,不会产生多余的拷贝。
MyClass createObject() {
MyClass obj("CreatedObject");
return obj; // 返回对象(编译器可能优化为返回值优化)
}
int main() {
MyClass newObj = createObject();
newObj.greet(); // 正常使用,无需担心对象被销毁
return 0;
}