int* p //声明指针变量
p //获取所存储的内存地址
*p //通过dereference解引用运算符* + 指针变量获取p存储的地址所指向的值
int& r = p //声明引用
&r //通过 & + 变量,取变量的内存地址(数组可以通过数组名也可通过&数组名[0]获取地址,因为 C++将数组名解释为首个元素的内存地址)
使用指针时*的位置,和使用引用时&的位置很重要,由上我们知道符号前有类型名时为声明,前无类型名时是取值,必要时可添加括号来明确语义
指针
定义:指针是一个变量,存储的是另一个变量的内存地址,只是一串整数。通过指针可以间接访问内存。
现在讨论的是原始指针(raw)不是智能指针(Smart)
为什么需要区别指针变量与普通变量:告知编译器存的是int的值还是另一个变量的地址,任何语言都有这个需求,只是将其包装为引用
指针类型
指针的类型不影响指针本身的大小和存储一串地址的本质,指针变量所占的内存空间只由系统的位数决定,与指针类型无关。sizeof(指针变量)计算的是指针变量所占用的内存大小,不管指针指向的是什么类型的数据,指针P本身所占的大小只与系统是32位还是64位有关,32位是4,64位是8
那指针的类型有什么用?指针的类型只是在读写內存里的数据时有意义
- 决定在解引用时引用多少个字节
- 指针+1-1操作时,内存跳过几个字节
不同类型的指针用来存放对应类型变量的地址
- char/int/float/double 类型的指针是为了存放对应类型变量的地址;
- 用 const 修饰指针变量有两种情况:
- const在*之前-常量指针:const int* pi或者int const* pi; 表示不能通过pi修改指针指向地址里的值;
- const在*之后-指针常量:int* const pi; 表示不能修改指针的值
本质避免混淆:( X )*,意味着存储X类型变量地址的指针,这是在没引入const前学习的知识。那么按照这个逻辑
- (const int / int const)* 代表存放了一个常量int类型变量地址的指针,指针本身不是常量,是指针存放的类型是常量。
- (int)* const,代表存放了一个int类型变量地址的指针,指针本身是常量,存放的仅仅是int
this指针
前言:学习底层语言能帮助理解那些层层封装成语法糖语言,由于c#没有指针的概念所以即使刘铁猛和赵新政两位恩师也没法展开进一步讲,而只是留下指向函数的调用者一句简单的话,当时很久都没太明白…
正如前面所说,所有类型的指针变量的内存大小与存放的变量无关,只和位数有关,我们可以测试一下:
class Student {
public:
int getAge() {
return age;
}
Student setAge(int age) {
this->age = age;
cout << "age:====" << age << endl;
return *this;
}
private:
int age;
};
int main() {
cout << sizeof(Student) << endl; //4
}
this指针不占用对象的大小,本身占8字节(64位)/4字节(32位)
每一个成员函数中都包含一个特殊的指针,该指针称为this,那其中存放的是谁的地址呢?下面进行测试:
void test() {
cout << "this 指针里面存放的地址是什么" << this << endl;
}
private:
int age;
};
int main() {
Student s;
s.test();
cout << "s 实例对象的地址:" << &s << endl;//相同的地址
}
this指针存储调用本函数的对象的首地址
- 静态函数没有this指针,静态成员最先被初始化,此时由于实例对象并不存在,所以静态函数的this没有可以指向的对象。此外C++类成员可以用.调用静态函数,但注意静态函数不依赖于对象,也不推荐这样写,一般通过类名::静态函数调用。
- 为什么要设计this的存在:区分成员函数和类内同名变量,返回自引用(事件)
终于明白C#中this指向函数调用者是什么意思了,我之前没有意识到this只能在函数中出现,函数一定有调用者(除非静态函数),而this就是指向调用者的指针。所以在函数内用对象.对象属性,和this.对象属性(如上)是一样的效果。通过一个指向类对象的指针访问类成员时用->,通过实例类对象访问类成员用.
此外this指针的类型为this *const,即指针的值不能修改,但指针指向的值可以修改,在普通成员函数中,编译器默认会这样理解 this
:
ClassName* const this;
编译器将无法将常量类的对象地址赋予给this指针因为指针类型不匹配,this指针只是针对非const成员函数
空指针 & 通用指针
void* 通用指针,可以指向任意类型的数据
空指针和下面的野指针都不是根据指针存储地址的存储类型而分类的,而是根据指针的存储地址分类的。任意类型指针只要指向一个特殊的指针值Null,其不代表任何有效内存地址,那么它就是空指针(非有效的内存地址,但其存在是允许的),用于初始化指针变量,避免指针指向未知的内存区域。
int* ptr = NULL;
野指针 Dangling Pointer
指向无效内存地址的指针,访问野指针会导致未定义行为(Undefined Behavior),可能导致程序崩溃、数据损坏
int* p; // 未初始化,野指针,危险!
int* p = NULL; // 安全,明确未指向任何内容
指针状态 | 是否空指针 | 是否安全 | 举例说明 |
---|---|---|---|
int* p = NULL; | ✅ 是空指针 | ✅ 安全(可检测) | 明确未指向任何有效内存 |
int* p; //未赋值 | ❌ 野指针 | ❌ 危险 | 指向未知内存,行为未定义 |
有以下引发原因:
- 指针指向的内存被释放(例如通过
free
或delete
),但指针本身没有被置为NULL
- 定义的
int
变量例如int x = 10
是存储在栈上的。栈上的变量生命周期与其作用域绑定,当作用域结束时(例如函数返回),栈上的变量会被自动销毁。 - 如果你使用
new
动态分配内存,例如int* ptr = new int(10);
,那么这个int
是存储在堆上的。堆上的变量生命周期由程+序员控制,在堆上分配的堆内存的生命周期不受函数作用域的限制,即使分配堆内存的函数已经执行完毕(即函数的作用域结束),堆内存仍然存在,直到程序员显式释放它(例如通过delete
或free
)。否则会导致内存泄漏
- 定义的
int* ptr = new int(10); // 动态分配内存
delete ptr; // 释放内存
// ptr 现在是一个野指针,虽然释放了它指向的内存,但是没有改变指针 ptr的值,它还保存着之前那块内存的地址
指针本身存储在栈上(可以在堆上放任何类型的对象),但它可以指向栈上的数据,也可以指向堆上的数据
2. 指针指向一个局部变量,但该变量已经离开了其作用域(例如函数返回后)。
int* getLocalPointer() {
int x = 10;
return &x; // 返回局部变量的地址
}
int* ptr = getLocalPointer();
// ptr 现在是一个野指针,因为 x 已经离开了作用域
x
是函数 getLocalPointer
中的局部变量,存储在栈上。当函数 getLocalPointer
返回时,x
的作用域结束,栈上的内存会被自动释放。函数返回 &x
,即局部变量 x
的地址。由于 x
的作用域已经结束,x
的内存已经被释放,因此返回的指针 &x
指向的内存是无效的。
建议:
- 在释放内存后,将指针置为
NULL
或nullptr
。
int* ptr = new int(10);
delete ptr;
ptr = nullptr; // 置空指针,避免野指针
- 避免返回局部变量的地址
int* getValidPointer() {
int* ptr = new int(10); // 动态分配内存
return ptr; // 返回指向堆内存的指针
}
- 使用智能指针(如
std::unique_ptr
或std::shared_ptr
)可以自动管理内存,避免野指针问题
#include <memory>
std::unique_ptr<int> ptr = std::make_unique<int>(10);
// 不需要手动释放内存,智能指针会自动管理
- 在使用指针之前,检查指针是否为
NULL
或nullptr
if (ptr != nullptr) {
// 安全使用指针
}
引用
- 引用是一个变量的别名,并不是独立的数据类型,必须与一种类型的数据相联系
- 所以必须在初始化时绑定到一个变量。
- 对变量声明一个引用,并不另辟内存单元,b和a代表同一单元
int &b=a; //正确,指定b是整型变量a的别名
int &b; //不正确,没指定b代表哪个变量
float a; int &b=a; //不正确,a与b类型不一致
- 声明一个变量的引用后,该引用变量不能再作为其它变量的别名
int a=3, b=4;
int &c=a; //正确,声明c为整型变量a的别名
int &c=b; //不正确,企图重新声明c为整型变量b的别名
- 一个变量可以有多个别名
int a=3;
int &b=a;
int &c=b; //声明c为整型引用b的别名
int a
int &b=a; //声明b是一个整型变量a的引用变量
a=a*a; // a的值变化了,b的值也应一起变化
cout<<a<<" "<<b<<endl;
b=b/5; // b的值变化了,a的值也应一起变化
cout<<b<<" "<<a<<e //a的值变化了,b的值也应一起变化
注意区分“声明引用变量”与“取地址操作”。当前面有类型符时,它是对“引用”的声明。如 int &b=a;
如果前面没有类型符,此时的&是“取地址运算符”。如 p=&b;
对于引用,没有什么是指针不能做的,但是引用能让代码看起来更简单,引用只是指针的语法糖
用 const 修饰引用变量也可以有两种情况
- const在&之前:const int & ri或者int const & ri; 表示不能通过ri修改所引用的变量的 值
- const在&之后:int & const ri; 表示不能修改引用。但好像没有意义,前面已经说过引用不能修改