1、C++指针
指针是一个变量,其值为另一个变量的地址,即,内存位置的直接地址。就像其他变量或常量一样,必须在使用指针存储其他变量地址之前,对其进行声明。
& 是一元运算符,返回操作数的内存地址。例如,如果 var
是一个整型变量,则 &var
是它的地址。该运算符与其他一元运算符具有相同的优先级,在运算时它是从右向左顺序进行的。
*
是一元运算符,返回操作数所指定地址的变量的值。指针声明示例如下:
int *ip; /* 一个整型的指针 */
double *dp; /* 一个 double 型的指针 */
float *fp; /* 一个浮点型的指针 */
char *ch; /* 一个字符型的指针 */
2、指针的使用
变量定义的时候给变量初始化,没有给指针初始化,就会出现野指针,该指针的指向并不是我们所希望的,一旦错误的释放了这个指针,就会发生内存的访问。指针使用之后,如果不释放指针所使用的内存,就会造成内存的泄露,这样就会有大量内存由于没能释放,别的程序不可以使用这部分内存,如果一个程序不停申请内存而不去释放内存,很快就会造成系统的崩溃。在使用指针时一定要判断指针是否为空,如果为空,则做相应的操作。如果不做判断,则可能错误的使用空指针。
例如,
#include <iostream>
using namespace std;
int main ()
{
int var = 20; /* 实际变量的声明 */
int *ip; /* 指针变量的声明 */
ip = &var; /* 在指针变量中存储 var 的地址 */
cout << "var 变量的地址: " << &var << endl;
/* 在指针变量中存储的地址 */
cout << "ip 变量存储的地址: " << ip << endl;
/* 使用指针访问值 */
cout << "*ip 变量的值: " << *ip << endl;
/*使用空指针初始化*/
int *pIntegerVal=NULL;
/*用变量初始化指针*/
int length=5;
int *pIntegerTemp=&length;
/*分配内存初始化指针*/
int *pInteger=(int*)malloc(10*sizeof(int));
//为指针分配大小为10个整数的内存空间。
/*内存申请和释放*/
if(pInteger != NULL)
{
free(pInteger);
pInteger=NULL;//指针释放之后并不为空,要设置其为空
}
pInteger=(int*)malloc(10*sizeof(int));
if(pInteger == NULL)
{
cout << "内存申请没有成功\n!";
}
return 0;
}
3、C++ 中的 NULL 指针
变量声明的时候,如果没有确切的地址可以赋值,为指针变量赋一个 NULL
值。赋为 NULL
值的指针被称为空指针。
NULL
指针是一个定义在标准库中的值为零的常量。这样能很好的避句野指针。
例如,
#include <iostream>
using namespace std;
int main ()
{
int *ptr = NULL;
cout << "ptr 的地址是 " << ptr << endl;
return 0;
}
注意:
大多数的操作系统上,程序不允许访问地址为 0
的内存,因为该内存是操作系统保留的。然而,内存地址 0
有特别重要的意义,它表明该指针不指向一个可访问的内存位置。
4、nullptr 关键字
nullptr
关键字就是表示空指针,C++11之前空指针都是NULL
。nullptr
表示空指针,不能转换为整型类型。为了向后兼容,C++11仍允许用0(NULL)
来表示空指针。nullptr
相比0(NULL)
具有更高的类型安全,推荐使用nullptr
。
#include <iostream>
using namespace std;
void foo(int* ptr) {
if (ptr == nullptr) {
std::cout << "Received null pointer" << std::endl;
} else {
std::cout << "Received non-null pointer" << std::endl;
}
}
int main() {
int* ptr = nullptr; // 使用 nullptr 初始化指针
foo(ptr); // 传递 nullptr
return 0;
}
5、C++ 智能指针
C++里面的四个智能指针: auto_ptr
, shared_ptr
, weak_ptr
, unique_ptr
其中后三个是c++11支持,并且第一个已经被11弃用。智能指针的作用是管理一个指针。因为可能申请的空间在函数结束时忘记释放,造成内存泄漏。使用智能指针可以很大程度上的避免这个问题,智能指针就是一个类,当超出了类的作用域,类会自动调用析构函数,析构函数会自动释放资源。所以智能指针的作用原理就是在函数结束时自动释放内存空间,不需要手动释放内存空间。
1)auto_ptr
std::auto_ptr
是 C++98/03 中的一种智能指针类型,它用于自动管理动态分配的内存。但从 C++11 开始,std::auto_ptr
被弃用,推荐使用 std::unique_ptr
和 std::shared_ptr
来替代。
#include <iostream>
#include <memory> // 包含 auto_ptr
using namespace std;
class MyClass {
public:
MyClass() {
std::cout << "MyClass Constructor\n";
}
~MyClass() {
std::cout << "MyClass Destructor\n";
}
void show() {
std::cout << "Hello from MyClass!\n";
}
};
int main() {
// 使用 auto_ptr
// 创建并初始化 auto_ptr
std::auto_ptr<MyClass> ptr1(new MyClass);
// 通过 auto_ptr 调用成员函数
ptr1->show();
// 转移所有权到 ptr2
std::auto_ptr<MyClass> ptr2 = ptr1;
// ptr1 已经变为 NULL,不能再使用
if (ptr1.get() == nullptr) {
std::cout << "ptr1 is null.\n";
}
// ptr2 拥有对象的所有权
ptr2->show();
// ptr2 超出作用域时,自动销毁 MyClass 对象
return 0;
}
2)shared_ptr
shared_ptr
采用引用计数的方式管理所指向的对象。当有一个新的shared_ptr
指向同一个对象时(复制shared_ptr
等),引用计数加1。当shared_ptr
离开作用域时,引用计数减1。当引用计数为0
时,释放所管理的内存。
#include <iostream>
#include <memory> // 包含 shared_ptr
using namespace std;
class MyClass {
public:
MyClass() {
std::cout << "MyClass constructor\n";
}
~MyClass() {
std::cout << "MyClass destructor\n";
}
void greet() {
std::cout << "Hello from MyClass!\n";
}
};
int main() {
// 创建一个 shared_ptr,指向一个动态分配的 MyClass 对象
std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>();
// 使用 shared_ptr 调用成员函数
ptr1->greet();
{
// 创建另一个 shared_ptr,指向同一个对象
std::shared_ptr<MyClass> ptr2 = ptr1;
// 使用 ptr2 调用成员函数
ptr2->greet();
// 当 ptr2 离开作用域时,它会自动释放所管理的对象
}
// ptr1 仍然持有指向 MyClass 对象的引用
// 当 ptr1 离开作用域时,MyClass 的析构函数会被调用,内存会被释放
return 0;
}
3)weak_ptr
weak_ptr
一般和shared_ptr
配合使用。它可以指向shared_ptr
所指向的对象,但是却不增加对象的引用计数。当出现weak_ptr
所指向的对象实际上已经被释放了的情况。weak_ptr
有一个lock
函数,尝试取回一个指向对象的shared_ptr
。std::weak_ptr
是 C++11 引入的智能指针,它用于解决 std::shared_ptr
引起的循环引用问题。std::weak_ptr
不会增加对象的引用计数,它只是观察 shared_ptr
指向的对象。当 shared_ptr
对象被销毁时,weak_ptr
自动失效。
#include <iostream>
#include <memory>
using namespace std;
class MyClass {
public:
MyClass(int val) : value(val) {
cout << "MyClass(" << value << ") created.\n";
}
~MyClass() {
cout << "MyClass(" << value << ") destroyed.\n";
}
void display() {
cout << "Value: " << value << endl;
}
private:
int value;
};
void demonstrateWeakPtr() {
shared_ptr<MyClass> sp1 = make_shared<MyClass>(10);
// weak_ptr 指向 sp1 管理的对象
weak_ptr<MyClass> wp1 = sp1;
if (auto sp2 = wp1.lock()) { // lock() 检查对象是否仍然存在
cout << "Weak pointer is valid.\n";
sp2->display();
} else {
cout << "Weak pointer is expired.\n";
}
sp1.reset(); // 释放资源
if (auto sp2 = wp1.lock()) {
cout << "Weak pointer is valid.\n";
sp2->display();
} else {
cout << "Weak pointer is expired.\n";
}
}
int main() {
demonstrateWeakPtr();
return 0;
}
4)unique_ptr
unique_ptr
对于所指向的对象, 指向的对象是独占的。不可以对unique_ptr
进行拷贝、赋值等操作,但是可以通过release
函数在unique_ptr
之间转移控制权。
#include <iostream>
#include <memory> // 包含 unique_ptr
using namespace std;
class Test {
public:
Test() {
std::cout << "Test 构造函数被调用!" << std::endl;
}
~Test() {
std::cout << "Test 析构函数被调用!" << std::endl;
}
void display() {
std::cout << "Test::display() 被调用!" << std::endl;
}
};
int main() {
// 手动分配并传递给 unique_ptr
std::unique_ptr<Test> ptr(new Test());
// 使用 ptr 访问 Test 对象
ptr->display();
// unique_ptr 自动释放内存
return 0;
}
6、动态内存分配 (new 和 delete)
C++ 的动态内存分配允许在程序运行时动态地分配内存,而不是在编译时就固定分配。通过 new
和 delete
操作符,可以在堆区(heap)上分配和释放内存。避免双重释放(Double Free),同一个内存块不能被 delete 两次,否则会导致未定义行为。通常在删除指针后,最好将其置为 nullptr,以防止误用。
#include <iostream>
int main() {
// 动态分配一个整数
int* ptr = new int;
*ptr = 100;
std::cout << "Value: " << *ptr << std::endl; // 输出 100
// 释放内存
delete ptr;
// 动态分配一个整数数组
int* arr = new int[3];
arr[0] = 10;
arr[1] = 20;
arr[2] = 30;
std::cout << "Array elements: ";
for (int i = 0; i < 3; ++i) {
std::cout << arr[i] << " "; // 输出 10 20 30
}
std::cout << std::endl;
// 释放数组内存
delete[] arr;
return 0;
}
7、指向指针的指针 (双重指针)
C++ 中的指向指针的指针(通常称为双重指针)是一个指针,它存储另一个指针的地址。双重指针的基本概念是,通过两层间接访问来操作存储在内存中的值。
#include <iostream>
using namespace std;
int main() {
int num = 10;
int* ptr = # // ptr 是指向 num 的指针
int** ptr2 = &ptr; // ptr2 是指向 ptr 的指针
// 输出 num: 10
cout << "num: " << num << endl;
// 输出 ptr: 10
cout << "ptr: " << *ptr << endl;
// 输出 ptr2: 10
cout << "ptr2: " << **ptr2 << endl;
// 修改 num 的值通过双重指针
**ptr2 = 20;
cout << "After modification:" << endl;
// 输出 num: 20
cout << "num: " << num << endl;
// 输出 ptr: 20
cout << "ptr: " << *ptr << endl;
// 输出 ptr2: 20
cout << "ptr2: " << **ptr2 << endl;
return 0;
}
1)双重指针与数组
双重指针经常用于处理二维数组(动态分配的数组)。二维数组实际上是一个数组的数组,可以通过双重指针访问每个元素。
#include <iostream>
using namespace std;
int main() {
int rows = 3, cols = 3;
// 动态分配二维数组
int** arr = new int*[rows]; // 创建指向 int* 的指针数组
for (int i = 0; i < rows; i++) {
arr[i] = new int[cols]; // 为每一行分配列
}
// 初始化数组
int value = 1;
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
arr[i][j] = value++;
}
}
// 打印数组
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
cout << arr[i][j] << " ";
}
cout << endl;
}
// 释放内存
for (int i = 0; i < rows; i++) {
delete[] arr[i]; // 删除每一行
}
delete[] arr; // 删除行指针数组
return 0;
}
2)双重指针作为函数参数
双重指针常用于函数中,以便修改指针本身或通过指针修改数据。
#include <iostream>
using namespace std;
void changePointer(int** ptr) {
// 修改 ptr 指向的指针的值
*ptr = new int(100); // 分配新的内存并赋值
}
int main() {
int* ptr = new int(10); // 初始指针指向值 10
cout << "Before: " << *ptr << endl; // 输出 10
changePointer(&ptr); // 传递 ptr 的地址
cout << "After: " << *ptr << endl; // 输出 100
delete ptr; // 释放内存
return 0;
}
8、常量指针和指针常量
C++ 中,常量指针和指针常量都是与指针相关的概念,区别在于它们限制了指针或指向的数据是否可以被修改。常量指针是指指向常量数据的指针,也就是说,通过常量指针不能修改指向的值,但指针本身(即指向的地址)是可以修改的。指针常量是指指针本身是常量的,即指针的值(即指向的地址)不能修改,但可以通过指针修改指向地址的内容。
#include <iostream>
using namespace std;
int main() {
int num1 = 10, num2 = 20;
// 常量指针(指向常量数据)
// ptr1 是一个常量指针,指向一个常量整数
const int* ptr1 = &num1;
std::cout << "Value pointed to by ptr1: "
<< *ptr1 << std::endl; // 输出 10
// *ptr1 = 30; // 错误:不能修改 ptr1 指向的内容
ptr1 = &num2; // 可以改变 ptr1 的指向
std::cout << "Now ptr1 points to: "
<< *ptr1 << std::endl; // 输出 20
// 指针常量(常量指针,指向非常量数据)
int* const ptr2 = &num1; // ptr2 是一个常量指针,指向一个可修改的整数
std::cout << "Value pointed to by ptr2: "
<< *ptr2 << std::endl; // 输出 10
*ptr2 = 30; // 可以修改 ptr2 指向的内容
std::cout
<< "After modifying the value pointed to by ptr2: "
<< *ptr2 << std::endl; // 输出 30
// ptr2 = &num2;
// 错误:不能修改 ptr2 的指向
return 0;
}