C/C++ 指针学习笔记 一个类比
张三的快递 : int 张三;
找个快递员 : int *快递员;
张三的货物 : int 货物;
快递员取货 : 快递员 = &货物;
快递员把货给张三 : 张三 = *快递员
1 2 3 4 5 6 7 8 9 10 11 12 13 { type a; type *p; int *p; p = &a; printf (p); }
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 { int a = 1025 ; int *p; p = &a; printf ("size of integer is %d bytes\n" , sizeof (int )); printf ("Address = %p, value = %d\n" , (void *)p, *p); printf ("Address = %p, value = %d\n" , (void *)(p + 1 ), *(p + 1 )); char *p0; p0 = (char *)p; printf ("size of char is %d bytes\n" , sizeof (char )); printf ("Address = %p, value = %d\n" , (void *)p0, *p0); printf ("Address = %p, value = %d\n" , (void *)(p0 + 1 ), *(p0 + 1 )); }
1 2 3 4 5 6 7 8 9 10 11 12 13 { int a = 10 ; int *p; p = &a; printf ("Address p is %p\n" , (void *)p); printf ("size of integer is %d bytes\n" , sizeof (int )); printf ("Address p+1 is %p\n" , (void *)(p + 1 )); }
C++ 引用 (Reference) C++ 引入了 引用 ,是一个变量的别名 。在功能上类似于指针,但使用上更简洁、更安全。
必须初始化 :引用在声明时必须被初始化。
不能为空 :没有 “null 引用” 这种东西。
不能重定向 :引用一旦绑定到一个变量,就不能再改为引用另一个变量。
1 2 3 4 5 6 7 8 9 int 货物 = 10 ;int & 张三 = 货物; 张三 = 20 ; int * 快递员 = &货物; *快递员 = 20 ; printf ("货物: %d\n" , 货物); printf ("张三: %d\n" , 张三);
引用最强大的用途是在函数参数中(见下文)。
point to pointer (指向指针的指针) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 { int x = 5 ; int *p = &x; *p = 6 ; int **q = &p; int ***r = &q; printf ("%p\n" , (void *)p); printf ("%d\n" , *p); printf ("%p\n" , (void *)q); printf ("%p\n" , (void *)*q); printf ("%d\n" , **q); printf ("%p\n" , (void *)r); printf ("%p\n" , (void *)*r); printf ("%p\n" , (void *)**r); printf ("%d\n" , ***r); ***r = 10 ; printf ("x = %d\n" , x); }
指针的应用 形参和实参 在 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 void Increment_Val (int a) { a = a + 1 ; } void Increment_Ptr (int *p) { *p = (*p) + 1 ; } int main () { int a = 10 ; Increment_Val(a); printf ("a after Val = %d\n" , a); Increment_Ptr(&a); printf ("a after Ptr = %d\n" , a); }
在 C++ 中,我们有了第三种也是更推荐的方式:引用传参 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 void Increment_Ref (int & a) { a = a + 1 ; } int main () { int a = 10 ; Increment_Ref (a); printf ("a after Ref = %d\n" , a); }
const 与指针/引用const 关键字用于防止数据被修改,在 C 和 C++ 中都至关重要。
常量引用 (Pass-by-Const-Reference): 这是 C++ 中 最高效且最安全 的传递大型只读对象(如 std::string 或大 struct)的方式。避免了拷贝(高效),同时保证函数不会修改原始数据(安全)。
1 2 3 4 5 6 void printString (const std::string& s) { printf ("%s\n" , s.c_str ()); }
const 与指针的两种关系 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 int x = 10 , y = 20 ;const int * p1 = &x;p1 = &y; int * const p2 = &x;*p2 = 15 ; const int * const p3 = &x;
数组 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 int A[] = {2 , 4 , 5 , 8 , 1 }; int i;int *p = A; for (int i = 0 ; i < 5 ; i++){ printf ("Address = %p\n" , (void *)&A[i]); printf ("Address = %p\n" , (void *)(A + i)); printf ("value = %d\n" , A[i]); printf ("value = %d\n" , *(A + i)); } p = p + 1 ;
数组作为函数的参数 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 int SumOfElements_Wrong (int A[]) { int i, sum = 0 ; int size = sizeof (A) / sizeof (A[0 ]); printf ("SOE_Wrong - Size of A = %d, size of A[0] = %d\n" , sizeof (A), sizeof (A[0 ])); for (i = 0 ; i < size; i++) { sum += A[i]; } return sum; } int SumOfElements_Correct (int A[], int size) { int i, sum = 0 ; printf ("SOE_Correct - Size of A = %d, size of A[0] = %d\n" , sizeof (A), sizeof (A[0 ])); for (i = 0 ; i < size; i++) { sum += A[i]; } return sum; } void Double (int * A, int size) { int i; for ( i = 0 ; i < size; i++) { A[i] = 2 * A[i]; } } int main () { int A[] = {1 , 2 , 3 , 4 , 5 }; int size = sizeof (A) / sizeof (A[0 ]); int total = SumOfElements_Correct(A, size); printf ("Total = %d\n" , total); Double(A, size); }
C++ 现代数组容器 在现代 C++ 中,我们强烈 推荐使用标准库容器来代替 C 风格的原始数组。
std::vector (动态数组)std::vector 是 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 39 #include <vector> #include <iostream> int SumOfElements (const std::vector<int >& vec) { int sum = 0 ; for (size_t i = 0 ; i < vec.size (); ++i) { sum += vec[i]; } sum = 0 ; for (int value : vec) { sum += value; } return sum; } void Double (std::vector<int >& vec) { for (int & value : vec) { value = value * 2 ; } } int main () { std::vector<int > V = {1 , 2 , 3 , 4 , 5 }; V.push_back (6 ); int total = SumOfElements (V); std::cout << "Total = " << total << std::endl; Double (V); }
std::array (固定大小数组)如果你需要一个固定大小、在栈上分配(像C数组一样)但又不想让退化为指针的数组,请使用 std::array (C++11)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <array> #include <iostream> void PrintArray (const std::array<int , 5 >& arr) { std::cout << "Size = " << arr.size () << std::endl; for (int value : arr) { std::cout << value << " " ; } } int main () { std::array<int , 5> A = {1 , 2 , 3 , 4 , 5 }; PrintArray (A); }
字符数组与字符串 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 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 #include <string.h> #include <stdio.h> int main () { { char C[5 ] = {'J' ,'O' ,'H' ,'N' ,'\0' }; printf ("Size in bytes = %d\n" , sizeof (C)); int len = strlen (C); printf ("Length = %d\n" , len); } { char C[5 ] = "JOHN" ; printf ("Size in bytes = %d\n" , sizeof (C)); printf ("Length = %d\n" , strlen (C)); } { char C1[6 ] = "Hello" ; char * C2; C2 = C1; printf ("%c\n" , C2[1 ]); C2[0 ] = 'A' ; printf ("%s\n" , C1); } } void print_string (char * C) { while (*C != '\0' ) { printf ("%c" , *C); C++; } printf ("\n" ); } int main_const_string () { const char * C = "hello" ; printf ("%s\n" , C); return 0 ; }
C++ std::string 和 std::string_view 在 C++ 中,你应该总是 优先使用 std::string 来处理字符串。
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 #include <string> #include <iostream> void print_string_cpp (const std::string& s) { std::cout << s << std::endl; std::cout << "Length = " << s.length () << std::endl; } void print_string_view (std::string_view sv) { std::cout << sv << std::endl; } int main () { std::string s1 = "Hello" ; std::string s2 = "World" ; std::string s3 = s1 + " " + s2; s1[0 ] = 'A' ; s1. push_back ('!' ); print_string_cpp (s1); print_string_view (s1); print_string_view ("I am a C-string" ); }
多维数组 一维数组 (回顾) 1 2 3 4 5 6 7 8 { int A[5 ] = {2 , 4 , 6 , 8 , 10 }; int * p = A; printf ("%p\n" , (void *)p); printf ("%d\n" , *p); printf ("%d\n" , *(p + 2 )); }
二维数组 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 int B[2 ][3 ] = { {1 ,2 ,3 }, {4 ,5 ,6 } };int (*p)[3 ] = B;printf ("%p\n" , (void *)B);printf ("%p\n" , (void *)&B[0 ]);printf ("%p\n" , (void *)*B);printf ("%p\n" , (void *)B[0 ]);printf ("%p\n" , (void *)&B[0 ][0 ]);printf ("%p\n" , (void *)(B + 1 ));printf ("%p\n" , (void *)&B[1 ]);printf ("%p\n" , (void *)*(B + 1 ));printf ("%p\n" , (void *)B[1 ]);printf ("%p\n" , (void *)&B[1 ][0 ]);printf ("%p\n" , (void *)(*(B + 1 ) + 2 ));printf ("%p\n" , (void *)(B[1 ] + 2 ));printf ("%p\n" , (void *)&B[1 ][2 ]);printf ("%d\n" , *(*B + 1 )); B[i][j] == *(B[i] + j) == *(*(B + i) + j)
三维数组 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 int C[3 ][2 ][2 ];int (*p)[2 ][2 ] = C;printf ("%p\n" , (void *)C);printf ("%p\n" , (void *)*C);void Func (int A[][2 ][2 ]) { }
C++ 多维数组的替代品
std::array 嵌套 :std::array<std::array<int, 3>, 2> B;
std::vector 嵌套 :std::vector<std::vector<int>> B;
优点:易于使用,可以是不规则数组(每行长度不同)。
缺点:数据在内存中不是连续存储的 ,可能导致缓存未命中,性能较差。
使用 std::vector 模拟 : 对于性能敏感的 2D 矩阵,通常使用一个**一维 vector**,并通过数学计算来模拟 2D 索引。1 2 3 4 int rows = 2 , cols = 3 ;std::vector<int > B (rows * cols) ; B[i * cols + j] = 5 ;
指针和动态内存
**栈区 (Stack)**:用于存放函数参数、局部变量。由编译器自动管理(函数开始时分配,结束时释放)。空间有限,速度快。
**堆区 (Heap)**:用于存放程序运行时动态分配的内存。由程序员手动管理。空间大,分配/释放较慢。
C 风格动态内存 (malloc, free) C 使用 stdlib.h 中的 malloc, calloc, realloc, free。
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 <stdio.h> #include <stdlib.h> int main () { int * p; p = (int *)malloc (sizeof (int )); if (p == NULL ) { printf ("Memory allocation failed!\n" ); return 1 ; } *p = 10 ; free (p); p = NULL ; int * A = (int *)calloc (20 , sizeof (int )); int * B = (int *)realloc (A, 25 * sizeof (int )); if (B != NULL ) { A = B; } free (A); }
C++ 动态内存 (new, delete) C++ 使用 new 和 delete 操作符来取代 malloc 和 free。更安全,因为会自动调用构造函数和析构函数 。
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 <string> int * p = new int ; *p = 10 ; delete p; std::string* s = new std::string ("Hello" ); delete s; int * A = new int [20 ]; delete [] A;
C++ 核心:RAII 和智能指针 (Smart Pointers) 手动管理 new 和 delete 仍然很容易出错(忘记 delete、异常导致 delete 未被执行等)。 现代 C++ 提倡 RAII (Resource Acquisition Is Initialization) 思想,即使用对象来管理资源 。当对象离开作用域时,其析构函数会自动释放资源。
智能指针 就是 RAII 的完美实践。在 <memory> 头文件中。
1. std::unique_ptr (C++11) - 独占所有权
unique_ptr 表示独占 所指向的对象。不能被复制,只能被移动 (Move)。这是 new 的首选替代品。
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 <memory> #include <iostream> void UseMyPointer (std::unique_ptr<int > ptr) { std::cout << "Using pointer, value: " << *ptr << std::endl; } int main () { std::unique_ptr<int > p1 = std::make_unique <int >(100 ); *p1 = 200 ; std::unique_ptr<int > p2 = std::move (p1); UseMyPointer (std::move (p2)); std::unique_ptr<int []> p_arr = std::make_unique <int []>(50 ); p_arr[0 ] = 10 ; return 0 ; }
2. std::shared_ptr (C++11) - 共享所有权
shared_ptr 使用引用计数 来管理对象。当最后一个 shared_ptr 被销毁时,对象才会被 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 #include <memory> #include <vector> std::shared_ptr<int > p_global; void Observe () { p_global = std::make_shared <int >(42 ); std::cout << "Use count: " << p_global.use_count () << std::endl; } int main () { Observe (); std::shared_ptr<int > p_local = p_global; std::cout << "Use count: " << p_global.use_count () << std::endl; { std::shared_ptr<int > p_inner = p_local; std::cout << "Use count: " << p_global.use_count () << 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 #include <stdio.h> #include <stdlib.h> void PrintHelloWorld () { printf ("Hello World\n" ); } int * Add (int * a, int * b) { int c = (*a) + (*b); return &c; } int main_bad () { int a = 2 , b = 4 ; int * ptr = Add(&a, &b); PrintHelloWorld(); printf ("Sum = %d\n" , *ptr); return 0 ; }
正确的做法 :
调用者传入用于存储结果的指针 (Pass-by-Pointer for output)。
函数返回在堆 上分配的内存(调用者必须 负责 free)。
(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 int * Add_Heap (int * a, int * b) { int * c = (int *)malloc (sizeof (int )); *c = (*a) + (*b); return c; } std ::unique_ptr <int > Add_Smart (int a, int b) { return std ::make_unique<int >(a + b); } int main_ok () { int a = 2 , b = 4 ; int * ptr_c = Add_Heap(&a, &b); printf ("Sum (C) = %d\n" , *ptr_c); free (ptr_c); std ::unique_ptr <int > ptr_cpp = Add_Smart(a, b); printf ("Sum (C++) = %d\n" , *ptr_cpp); return 0 ; }
函数指针 函数指针存放了函数在内存中的入口地址 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <stdio.h> int Add (int a, int b) { return a + b; } int main () { int c; int (*p)(int , int ); p = &Add; c = (*p)(2 , 3 ); printf ("%d\n" , 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 #include <stdio.h> int compare (int a, int b) { if (a > b) return 1 ; else return -1 ; } void BubbleSort (int *A, int n, int (*compare_func)(int , int )) { int i, j, temp; for (i = 0 ; i < n; i++) for (j = 0 ; j < n - 1 ; j++) { if (compare_func(A[j], A[j + 1 ]) > 0 ) { temp = A[j]; A[j] = A[j + 1 ]; A[j + 1 ] = temp; } } } int main_sort () { int i, A[] = {3 , 2 , 1 , 5 , 4 , 6 }; BubbleSort(A, 6 , compare); for (i = 0 ; i < 6 ; i++) printf ("%d " , A[i]); return 0 ; }
C++ 可调用对象 (Callables) 在 C++ 中,函数指针的概念被扩展为**可调用对象 (Callable)**。包括:
函数指针 (C 风格)
函数对象 (Functor):重载了 operator() 的类/结构体。
Lambda 表达式 (C++11):匿名的、内联的函数。
1. Lambda 表达式 (C++11) 这是 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 #include <algorithm> #include <vector> int main () { std::vector<int > V = {3 , 2 , 1 , 5 , 4 , 6 }; std::sort (V.begin (), V.end (), [](int a, int b) { return a < b; }); int offset = 10 ; std::for_each(V.begin (), V.end (), [offset](int & val) { val += offset; }); }
2. std::function (C++11) 如果你需要一个通用的”容器”来存储任何类型 的可调用对象(函数指针、Lambda、Functor),请使用 std::function (在 <functional> 头文件中)。
是 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 #include <functional> #include <vector> #include <iostream> void BubbleSort_CPP (std::vector<int >& A, std::function<bool (int , int )> compare_func) { int n = A.size (); for (int i = 0 ; i < n; i++) for (int j = 0 ; j < n - 1 ; j++) { if (compare_func (A[j], A[j + 1 ])) { std::swap (A[j], A[j + 1 ]); } } } bool GreaterThan (int a, int b) { return a > b; }int main () { std::vector<int > V = {3 , 2 , 1 , 5 , 4 , 6 }; BubbleSort_CPP (V, [](int a, int b) { return a > b; }); BubbleSort_CPP (V, GreaterThan); }