【C++入门】(十三)开发高级指针
1.1 在堆中创建对象
-
定义了类型 Cat 后,便可声明一个指向这种对象的指针,并在堆中实例化一个 Cat 对象,就像在栈中实例化一样
Cat *pCat = new Cat; // 这将调用默认构造函数 —— 不接受任何参数的构造函数 // 每当在堆或栈中创建对象时,都将调用构造函数
-
为什么要在堆中声明对象?
函数返回后,在堆中声明的对象依然存在。这种对象还是动态的;此外,在堆中声明的对象可用来创建复杂的数据结构
1.2 删除对象
-
对指向堆中对象的指针调用 delete 时,将调用对象的析构函数,然后释放内存。这让类有机会执行清理工作,就像销毁栈中的对象一样
delete pCat;
#include <iostream> using namespace std; class SimpleCat { public: SimpleCat(); ~SimpleCat(); private: int itsAge; }; SimpleCat::SimpleCat() { cout << "调用构造函数SimpleCat" << endl; itsAge = 1; } SimpleCat::~SimpleCat() { cout << "调用析构函数~SimpleCat" << endl; } int main() { cout << "1. 创建一个名为 Frisky 的 SimpleCat类型对象" << endl; SimpleCat Frisky; cout << "2. 在堆中new一个SimpleCat对象,并让指针pRags指向它" << endl; SimpleCat* pRags = new SimpleCat; cout << "3. 删除指针 pRags" << endl; delete pRags; cout << "4. 函数结束时,Frisky不再在作用域内" << endl; return 0; } //1. 创建一个名为 Frisky 的 SimpleCat类型对象 //调用构造函数SimpleCat //2. 在堆中new一个SimpleCat对象,并让指针pRags指向它 //调用构造函数SimpleCat //3. 删除指针 pRags //调用析构函数~SimpleCat //4. 函数结束时,Frisky不再在作用域内 //调用析构函数~SimpleCat
-
如果程序员没有显性地执行 delete 命令,对象将在不再在作用域时被删除(调用析构函数)
如果对象是在 main() 中创建的,且程序员没有显性地删除它,则在 main() 结束后,将调用析构函数
2. 如何高效地使用指针?
2.1 使用指针访问数据成员
-
对于在栈中创建的 Cat 对象,使用句点运算符
.
来访问其成员数据和成员函数(*pRags).GetAge(); //要访问堆中 Cat 对象,必须对指针解除引用,并对指针指向的对象使用句点运算符 // (*pRags) 用括号括起来,以确保对pRags解引用后再访问GetAge()
-
C++还提供一种简洁的间接访问运算符
-> 指向运算符
pRags->GetAge();
2.2 堆中的数据成员
-
类可能有一个或多个数据成员为指针,并指向堆中的对象。
可在构造函数或成员函数中分配内存,并在析构函数中释放内存
#include <iostream> class SimpleCat { public: SimpleCat(); ~SimpleCat(); private: int* itsAge; int* itsWeight; }; SimpleCat::SimpleCat() { } SimpleCat::~SimpleCat() //2. 需要自己写析构函数,否则会造成内存泄露 { delete itsAge; delete itsWeight; } int main() { SimpleCat* Frisky = new SimpleCat; delete Frisky; //1. delete只删除指向堆内存的指针,而不会删除堆中的内容 return 0; }
2.3 this指针
-
每个类成员函数都有一个隐藏的参数 ——
this指针
,它指向用于调用函数的对象(通常只是调用函数并设置成员变量的情况下,并不需要this指针,偶尔需要访问对象本身时,this指针将很有用)
class Rectangle { public: Rectangle(); ~Rectangle(); //显示的使用了 this指针 来访问 Rectangle对象 的成员变量,没啥软用 void SetLength(int length) { this->Length = length; } int GetLength() const { return this->Length; } private: int Length; }; //不用管 this指针的创建和删除,编译器会负责善后
2.4 悬摆指针
-
悬摆指针 / 野指针 / 迷失指针 是bug之源(之一)
-
对指针调用 delete(从而释放它指向的内存)后,如果没有重新赋值就使用
,将导致悬摆指针 -
对指针调用 delete 后,千万不要使用它。该指针仍指向原来的内存区域(但编译器可能在这里存储了其他数据),使用该指针可能导致程序崩溃。出于安全考虑,
删除指针后,应将其设为nullptr
2.5 const指针和 const成员函数
-
const int *p1;
指针指向的对象是常量int * const p2;
指针本身是常量const int *p1; // p1 是指向整型常量的指针,使用该指针 不能修改它指向的值 // 不能写 *p1 = 5; 这样的代码
int * const p2; // p2 是指向整型的常量指针,使用该指针 可修改它指向的整型变量,但不能指向其他变量,不能给常量指针重新赋值 // 不能写 *p2 = &x; 这样的代码
const int * const p3; // p3 是指向整型常量的常量指针,使用该指针 不能修改它指向的值,也不能让它指向其他变量
-
const 常量成员函数
class Rectangle { public: //void SetLength(int length) const { itsLength = length; } //报错,常量成员函数不可修改 int GetLength() const { return itsLength; } private: int itsLength; int itsWidth; };
-
如果声明了一个指向常量对象的指针,那么使用该指针只能调用常量函数
class Rectangle { public: int func1() const { return itsLength; } //const常量函数 int func2() { return itsLength; } private: int itsLength = 66; }; int main() { //如果声明了一个指向常量对象的指针,那么使用该指针只能调用常量函数 const Rectangle* p2 = new Rectangle; cout << "指向常量对象的指针p2,只能调用常量函数"<< p2->func1() << endl; //cout << "指向常量对象的指针p2,只能调用常量函数"<< p2->func2() << endl; //对象含有与成员 函数 "Rectangle::func2" 不兼容的类型限定符 return 0; }
-
将对象声明为常量时,实际上是将 this 声明为指向常量对象的指针。
常量 this指针 只能用于调用常量成员函数
3. 使用指针时如何避免内存问题?
-
指针定义后要初始化,没有指向时指向NULL;
-
指针赋值时一定要保证类型匹配
由于指针类型确定指针所指向对象的类型,因此初始化或赋值时必须保证类型匹配;
-
两个指针最好不要指向同一块内存,一个指针的释放会导致另一个指针成为悬垂指针,造成不堪设想的后果;
-
释放指针内存后要将该指针置空;
-
指针分配成功后才可以使用,要走注意分配后的大小,不要越界。
4.总结
-
指针可指向整型等简单数据类型,也可以指向对象
-
可在堆中创建对象,并将其删除(可声明指向类的对象的指针,并在堆中实例化对象)
-
类的数据成员可以是指向堆中对象的指针
-
可在类的构造函数或其他函数中分配内存,并在析构函数中将其释放
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· 写一个简单的SQL生成工具