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; }