第9章 数组和指针 数组 数组的基本概念 数组是一种数据结构,用于存储相同类型的多个元素。在C++中,数组的大小在声明时必须确定,且在程序运行过程中不能改变。
数组的声明和初始化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 #include <iostream> int main () { int numbers[5 ]; numbers[0 ] = 10 ; numbers[1 ] = 20 ; numbers[2 ] = 30 ; numbers[3 ] = 40 ; numbers[4 ] = 50 ; int scores[3 ] = {95 , 87 , 91 }; double prices[] = {19.99 , 29.99 , 39.99 , 49.99 }; char message[] = "Hello, World!" ; std::cout << "Numbers: " ; for (int i = 0 ; i < 5 ; i++) { std::cout << numbers[i] << " " ; } std::cout << std::endl; std::cout << "Scores: " ; for (int score : scores) { std::cout << score << " " ; } std::cout << std::endl; return 0 ; }
数组的特性 连续存储 :数组元素在内存中是连续存储的固定大小 :数组大小在声明时确定,不能动态改变索引访问 :使用下标(从0开始)访问数组元素边界检查 :C++不自动检查数组边界,需要程序员自己确保不越界多维数组 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #include <iostream> int main () { int matrix[2 ][3 ] = { {1 , 2 , 3 }, {4 , 5 , 6 } }; std::cout << "matrix[0][0]: " << matrix[0 ][0 ] << std::endl; std::cout << "matrix[1][2]: " << matrix[1 ][2 ] << std::endl; std::cout << "Matrix: " << std::endl; for (int i = 0 ; i < 2 ; i++) { for (int j = 0 ; j < 3 ; j++) { std::cout << matrix[i][j] << " " ; } std::cout << std::endl; } int cube[2 ][2 ][2 ] = { {{1 , 2 }, {3 , 4 }}, {{5 , 6 }, {7 , 8 }} }; return 0 ; }
指针 指针的基本概念 指针是一种变量,用于存储内存地址。通过指针,可以间接访问和修改内存中的数据。
指针的声明和初始化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #include <iostream> int main () { int x = 100 ; int * ptr = &x; std::cout << "x = " << x << std::endl; std::cout << "&x = " << &x << std::endl; std::cout << "ptr = " << ptr << std::endl; std::cout << "*ptr = " << *ptr << std::endl; *ptr = 200 ; std::cout << "After modification: " << std::endl; std::cout << "x = " << x << std::endl; std::cout << "*ptr = " << *ptr << std::endl; int * nullPtr = nullptr ; int * oldNullPtr = NULL ; return 0 ; }
指针的运算 指针算术是C++中强大的特性,它允许指针根据所指向类型的大小进行算术运算:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #include <iostream> int main () { int numbers[] = {10 , 20 , 30 , 40 , 50 }; int * ptr = numbers; std::cout << "*ptr = " << *ptr << std::endl; ptr++; std::cout << "*ptr = " << *ptr << std::endl; ptr += 2 ; std::cout << "*ptr = " << *ptr << std::endl; ptr--; std::cout << "*ptr = " << *ptr << std::endl; int * start = numbers; int * end = numbers + 5 ; std::cout << "Array elements: " ; while (start < end) { std::cout << *start << " " ; start++; } std::cout << std::endl; return 0 ; }
指针算术的规则 指针与整数的加减 :
ptr + n:指针向后移动n个元素,实际移动的字节数为 n * sizeof(*ptr)ptr - n:指针向前移动n个元素,实际移动的字节数为 n * sizeof(*ptr)ptr++/++ptr:指针向后移动1个元素ptr--/--ptr:指针向前移动1个元素指针之间的减法 :
ptr2 - ptr1:计算两个指针之间的元素个数,结果为整数类型(ptrdiff_t)要求两个指针指向同一个数组或同一个对象的不同部分 指针比较运算 :
==、!=:判断两个指针是否指向同一位置<、<=、>、>=:判断指针在内存中的相对位置要求两个指针指向同一个数组或同一个对象的不同部分 指针算术的应用场景 数组遍历 :使用指针算术遍历数组元素动态内存管理 :在动态分配的内存块中导航字符串处理 :操作C风格字符串数据结构实现 :实现链表、树等数据结构性能优化 :指针算术通常比数组下标访问更快指针算术的注意事项 越界访问 :指针算术可能导致越界访问,应确保指针始终在有效范围内类型安全 :指针算术依赖于指针的类型,不同类型的指针算术结果不同空指针 :对空指针进行算术运算会导致未定义行为野指针 :对野指针进行算术运算会导致未定义行为void指针 :void指针不支持算术运算,因为它的大小未知指针算术示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <iostream> int main () { double values[] = {1.1 , 2.2 , 3.3 , 4.4 , 5.5 }; double * p = values; std::cout << "p = " << p << ", *p = " << *p << std::endl; p += 2 ; std::cout << "p += 2: " << p << ", *p = " << *p << std::endl; double * start = values; double * end = values + 5 ; ptrdiff_t count = end - start; std::cout << "Elements count: " << count << std::endl; if (start < end) { std::cout << "start is before end" << std::endl; } return 0 ; }
指针与数组的关系 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <iostream> int main () { int numbers[] = {10 , 20 , 30 , 40 , 50 }; std::cout << "*numbers = " << *numbers << std::endl; std::cout << "*(numbers + 1) = " << *(numbers + 1 ) << std::endl; int * ptr = numbers; std::cout << "ptr[0] = " << ptr[0 ] << std::endl; std::cout << "ptr[2] = " << ptr[2 ] << std::endl; std::cout << "*(numbers + 3) = " << *(numbers + 3 ) << std::endl; return 0 ; }
指针与函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #include <iostream> void increment (int * value) { (*value)++; } int * createArray (int size) { int * arr = new int [size]; for (int i = 0 ; i < size; i++) { arr[i] = i * 10 ; } return arr; } int main () { int x = 5 ; std::cout << "Before: x = " << x << std::endl; increment (&x); std::cout << "After: x = " << x << std::endl; int * numbers = createArray (5 ); std::cout << "Array elements: " ; for (int i = 0 ; i < 5 ; i++) { std::cout << numbers[i] << " " ; } std::cout << std::endl; delete [] numbers; return 0 ; }
指向指针的指针 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include <iostream> int main () { int x = 100 ; int * ptr = &x; int ** ptrToPtr = &ptr; std::cout << "x = " << x << std::endl; std::cout << "*ptr = " << *ptr << std::endl; std::cout << "**ptrToPtr = " << **ptrToPtr << std::endl; **ptrToPtr = 200 ; std::cout << "After modification: " << std::endl; std::cout << "x = " << x << std::endl; std::cout << "*ptr = " << *ptr << std::endl; std::cout << "**ptrToPtr = " << **ptrToPtr << std::endl; return 0 ; }
动态内存分配 new 和 delete 运算符 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 #include <iostream> int main () { int * pInt = new int ; *pInt = 42 ; std::cout << "*pInt = " << *pInt << std::endl; delete pInt; int size = 5 ; int * pArray = new int [size]; for (int i = 0 ; i < size; i++) { pArray[i] = i * 10 ; } std::cout << "Array elements: " ; for (int i = 0 ; i < size; i++) { std::cout << pArray[i] << " " ; } std::cout << std::endl; delete [] pArray; class Person { public : std::string name; int age; Person (std::string n, int a) : name (n), age (a) { std::cout << "Person constructor called" << std::endl; } ~Person () { std::cout << "Person destructor called" << std::endl; } void display () { std::cout << "Name: " << name << ", Age: " << age << std::endl; } }; Person* pPerson = new Person ("Alice" , 30 ); pPerson->display (); delete pPerson; return 0 ; }
智能指针 智能指针是C++11引入的一种RAII(资源获取即初始化)机制,用于自动管理动态内存,避免内存泄漏。智能指针本质上是一个包装了裸指针的类,通过析构函数自动释放所管理的内存。
智能指针的种类 std::unique_ptr :独占所有权的智能指针std::shared_ptr :共享所有权的智能指针std::weak_ptr :不增加引用计数的shared_ptr观察者std::unique_ptr std::unique_ptr是一种独占所有权的智能指针,同一时间只能有一个unique_ptr指向同一个对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 #include <iostream> #include <memory> class Person {public : std::string name; int age; Person (std::string n, int a) : name (n), age (a) { std::cout << "Person constructor called for " << name << std::endl; } ~Person () { std::cout << "Person destructor called for " << name << std::endl; } void display () { std::cout << "Name: " << name << ", Age: " << age << std::endl; } }; int main () { std::unique_ptr<Person> p1 (new Person("Alice" , 30 )) ; p1->display (); auto p2 = std::make_unique <Person>("Bob" , 25 ); p2->display (); std::unique_ptr<Person> p3 = std::move (p1); if (p1) { p1->display (); } else { std::cout << "p1 is empty" << std::endl; } p3->display (); auto pArray = std::make_unique <Person[]>(3 ); return 0 ; }
std::shared_ptr std::shared_ptr是一种共享所有权的智能指针,多个shared_ptr可以指向同一个对象,通过引用计数管理对象的生命周期:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include <iostream> #include <memory> int main () { std::shared_ptr<Person> p1 (new Person("Charlie" , 35 )) ; std::cout << "Reference count: " << p1. use_count () << std::endl; auto p2 = std::make_shared <Person>("David" , 40 ); std::cout << "Reference count: " << p2. use_count () << std::endl; std::shared_ptr<Person> p3 = p2; std::cout << "Reference count after copy: " << p2. use_count () << std::endl; std::shared_ptr<Person> p4 = std::move (p1); std::cout << "p1 use_count: " << p1. use_count () << std::endl; std::cout << "p4 use_count: " << p4. use_count () << std::endl; return 0 ; }
std::weak_ptr std::weak_ptr是一种不增加引用计数的智能指针,用于观察shared_ptr管理的对象,避免循环引用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 #include <iostream> #include <memory> class Node {public : int value; std::shared_ptr<Node> next; std::weak_ptr<Node> prev; Node (int v) : value (v) { std::cout << "Node constructor called for " << value << std::endl; } ~Node () { std::cout << "Node destructor called for " << value << std::endl; } }; int main () { auto n1 = std::make_shared <Node>(1 ); auto n2 = std::make_shared <Node>(2 ); n1->next = n2; n2->prev = n1; std::weak_ptr<Node> wp = n1; if (auto sp = wp.lock ()) { std::cout << "Node value: " << sp->value << std::endl; } else { std::cout << "Node no longer exists" << std::endl; } std::cout << "n1 use_count: " << n1. use_count () << std::endl; std::cout << "n2 use_count: " << n2. use_count () << std::endl; return 0 ; }
智能指针的原理 RAII机制 :智能指针在构造时获取资源,在析构时释放资源所有权管理 :unique_ptr:独占所有权,不允许复制,只允许移动 shared_ptr:共享所有权,通过引用计数追踪所有者数量 weak_ptr:不拥有所有权,只观察shared_ptr管理的对象 析构函数 :智能指针的析构函数自动调用delete或delete[]释放内存智能指针的使用场景 std::unique_ptr :
独占资源的场景 作为函数返回值 作为类成员变量 管理局部动态对象 std::shared_ptr :
多个所有者共享资源的场景 跨作用域共享对象 复杂数据结构(如链表、树)中的节点 长时间存在的对象 std::weak_ptr :
打破shared_ptr的循环引用 观察对象而不延长其生命周期 缓存场景 智能指针的最佳实践 优先使用make_unique和make_shared :
更安全:避免内存泄漏(如果在构造过程中抛出异常) 更高效:make_shared只分配一次内存(对象和控制块) 选择合适的智能指针 :
默认使用unique_ptr,当需要共享所有权时使用shared_ptr 避免不必要的shared_ptr,因为引用计数有开销 避免裸指针与智能指针混用 :
不要用智能指针管理已经由裸指针管理的内存 不要从智能指针获取裸指针后手动释放 注意循环引用 :
正确处理数组 :
unique_ptr支持数组版本:std::unique_ptr<T[]> shared_ptr需要自定义删除器来管理数组 传递智能指针 :
对于unique_ptr,使用移动语义传递 对于shared_ptr,根据需要传递值或引用 智能指针的性能考量 内存开销 :
unique_ptr:几乎无额外开销,与裸指针大小相同 shared_ptr:有额外开销(控制块、引用计数) weak_ptr:与shared_ptr类似,需要访问控制块 时间开销 :
unique_ptr:操作几乎无开销 shared_ptr:引用计数的增减需要原子操作,有一定开销 make_shared:比直接使用new更高效 优化策略 :
优先使用unique_ptr 合理使用make_shared减少内存分配 避免频繁的shared_ptr复制 注意移动语义的使用 数组和指针的安全性 常见错误 空指针解引用 :尝试访问空指针指向的内存野指针 :指针指向已经释放的内存内存泄漏 :动态分配的内存没有释放数组越界 :访问超出数组范围的元素悬挂引用 :引用指向已经销毁的对象安全实践 初始化指针 :总是将指针初始化为nullptr或有效的内存地址检查空指针 :在使用指针前检查是否为nullptr使用智能指针 :优先使用unique_ptr和shared_ptr管理动态内存避免裸指针 :尽量减少使用裸指针,尤其是在管理动态内存时使用std::array :对于固定大小的数组,使用std::array替代内置数组使用std::vector :对于可变大小的数组,使用std::vector替代内置数组边界检查 :在访问数组元素时确保不越界使用RAII :使用资源获取即初始化的原则管理资源数组和指针的应用 字符串处理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 #include <iostream> #include <cstring> int main () { char str1[] = "Hello" ; char str2[20 ]; std::strcpy (str2, str1); std::cout << "str2: " << str2 << std::endl; std::strcat (str2, " World" ); std::cout << "str2 after concatenation: " << str2 << std::endl; std::cout << "Length of str2: " << std::strlen (str2) << std::endl; char str3[] = "Hello" ; char str4[] = "World" ; int result = std::strcmp (str3, str4); if (result < 0 ) { std::cout << str3 << " is less than " << str4 << std::endl; } else if (result > 0 ) { std::cout << str3 << " is greater than " << str4 << std::endl; } else { std::cout << str3 << " is equal to " << str4 << std::endl; } return 0 ; }
函数参数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 #include <iostream> void printArray (int arr[], int size) { for (int i = 0 ; i < size; i++) { std::cout << arr[i] << " " ; } std::cout << std::endl; } void modifyArray (int * arr, int size) { for (int i = 0 ; i < size; i++) { arr[i] *= 2 ; } } template <size_t N>void printArrayWithReference (int (&arr)[N]) { for (int i = 0 ; i < N; i++) { std::cout << arr[i] << " " ; } std::cout << std::endl; } int main () { int numbers[] = {1 , 2 , 3 , 4 , 5 }; int size = sizeof (numbers) / sizeof (numbers[0 ]); std::cout << "Original array: " ; printArray (numbers, size); modifyArray (numbers, size); std::cout << "Modified array: " ; printArray (numbers, size); std::cout << "Using reference: " ; printArrayWithReference (numbers); return 0 ; }
多维数组和指针 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 #include <iostream> void print2DArray (int arr[][3 ], int rows) { for (int i = 0 ; i < rows; i++) { for (int j = 0 ; j < 3 ; j++) { std::cout << arr[i][j] << " " ; } std::cout << std::endl; } } void printUsingDoublePointer (int ** arr, int rows, int cols) { for (int i = 0 ; i < rows; i++) { for (int j = 0 ; j < cols; j++) { std::cout << arr[i][j] << " " ; } std::cout << std::endl; } } int main () { int matrix[2 ][3 ] = {{1 , 2 , 3 }, {4 , 5 , 6 }}; std::cout << "Static 2D array: " << std::endl; print2DArray (matrix, 2 ); int rows = 2 ; int cols = 3 ; int ** dynamicMatrix = new int *[rows]; for (int i = 0 ; i < rows; i++) { dynamicMatrix[i] = new int [cols]; } int value = 1 ; for (int i = 0 ; i < rows; i++) { for (int j = 0 ; j < cols; j++) { dynamicMatrix[i][j] = value++; } } std::cout << "Dynamic 2D array: " << std::endl; printUsingDoublePointer (dynamicMatrix, rows, cols); for (int i = 0 ; i < rows; i++) { delete [] dynamicMatrix[i]; } delete [] dynamicMatrix; return 0 ; }
总结 数组和指针是C++中非常重要的概念,它们密切相关且功能强大。通过本章的学习,你应该掌握了:
数组的基本概念 :声明、初始化、访问和遍历指针的基本概念 :声明、初始化、解引用和算术运算数组和指针的关系 :数组名作为指针、指针作为数组使用动态内存分配 :使用new和delete管理动态内存智能指针 :使用unique_ptr和shared_ptr自动管理内存安全性 :避免常见的指针和数组错误应用 :字符串处理、函数参数、多维数组等数组和指针是C++的基础,也是理解更高级特性(如引用、模板、STL等)的关键。通过不断练习和实践,你会逐渐掌握它们的使用技巧,并能够编写更高效、更安全的代码。