什么是指针
指针表示存储某一对象(通常是变量、函数等)的内存地址。
普通指针
指针与变量
符号&表示引用,变量名左侧的&表示取该变量的地址。
符号表示解引用,变量名左侧的 表示取该变量的值。
声明一个变量在类型名右侧用*表示声明该类型的指针 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 int main (int argc, char * argv[]) { std::cout<<"---------指针---------" << '\n' ; int *pc, c; c = 5 ; cout << "Address of c (&c): " << &c << endl; cout << "Value of c (c): " << c << endl << endl; pc = &c; cout << "pc持有的指针地址(pc): " << pc << endl; cout << "地址指针pc持有的值(*pc): " << *pc << endl << endl; c = 11 ; cout << "地址指针pc持有(pc): " << pc << endl; cout << "地址指针pc持有的内容(*pc): " << *pc << endl << endl; *pc = 2 ; cout << "Address of c (&c): " << &c << endl; cout << "Value of c (c): " << c << endl << endl; return 0 ; }
输出:
1 2 3 4 5 6 7 8 9 10 11 12 ---------指针--------- Address of c (&c) : 000000 D3BDB3F604 Value of c (c): 5 pc持有的指针地址(pc): 000000 D3BDB3F604 地址指针pc持有的值(*pc): 5 地址指针pc持有(pc): 000000 D3BDB3F604 地址指针pc持有的值(*pc): 11 Address of c (&c): 000000 D3BDB3F604 Value of c (c): 2
指针与数组
数组中元素的访问
一维数组
数组的变量名本质是数组第一个元素的指针。除了可以通过[]访问数组元素,还可以使用指针的位移访问元素。
**PS.**C++中数组访问超出范围后仍然可以访问,但只是返回一个未初始化的值和一个连续的地址。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 int main (int argc, char * argv[]) { int a[6 ] = {0 ,1 ,2 ,3 ,4 ,5 }; cout<<"a[0] value: " << a[0 ] <<"\n" ; cout<<"*a value: " << *a <<"\n" ; cout<<"a address: " << a <<"\n" ; cout<<"&a address: " << &a <<"\n" ; cout<<"&a[0] address: " << &a[0 ] <<"\n" <<"\n" ; cout<<"a[3] value: " << a[3 ] <<"\n" ; cout<<"a+3 address: " << (a+3 ) <<"\n" ; cout<<"&a[3] address: " << &a[3 ] <<"\n" <<"\n" ; cout<<"&a[5] address: " << &a[5 ] <<"\n" ; cout<<"a[5] value: " << a[5 ] <<"\n" ; cout<<"&a[6] address: " << &a[6 ] <<"\n" ; cout<<"a[6] value: " << a[6 ] <<"\n" ; }
输出:
1 2 3 4 5 6 7 8 9 10 11 12 a[0 ] value: 0 *a value: 0 a address: 000000E1082F FB78 &a address: 000000E1082F FB78 &a[0 ] address: 000000E1082F FB78 a[3 ] value: 3 a+3 address: 000000E1082F FB84 &a[5 ] address: 000000E1082F FB8C a[5 ] value: 5 &a[6 ] address: 000000E1082F FB90 a[6 ] value: -858993460
多维数组
下面示例均以二维数组为例。
数组名指向多维数组第一个子数组的首位元素的地址。
多为数组的声明不能省略最后一个维度的数量。省略其他维度时编译器会推断总共需要申请多少内存。1 2 3 int a[][][2 ] = {{0 ,2 ,1 },{0 ,1 }}; int b[][2 ] = {{0 ,2 },{0 ,1 }}; int c[2 ][] = {{0 ,2 },{0 ,1 }};
1 2 3 4 5 6 7 8 9 10 int main (int argc, char * argv[]) { int a[2 ][3 ] = {{0 ,1 ,2 },{4 ,5 ,6 }}; cout<<"a[1][2] value: " <<a[1 ][2 ]<< '\n' ; cout<<"*(*(a+1)+2) value: " <<*(*(a+1 )+2 )<< '\n' <<'\n' ; cout<<"a address: " << a << '\n' ; cout<<"&a[0] address: " << &a[0 ]<< '\n' ; cout<<"&a[0][0] address: " << &a[0 ][0 ]<< '\n' ; }
输出:
1 2 3 4 5 6 a[1 ][2 ] value: 6 *(*(a+1 )+2 ) value: 6 a address: 000000 9C1993F528 &a[0 ] address: 000000 9C1993F528 &a[0 ][0 ] address: 000000 9C1993F528
数组与函数
下面是一段错误代码,结果并不符合预期
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 int * matrix_multiple (int a[3 ], int b[3 ]) { int res[3 ]; res[0 ] = a[0 ]*b[0 ]; res[1 ] = a[1 ]*b[1 ]; res[2 ] = a[2 ]*b[2 ]; return res; } int main (int argc, char * argv[]) { int a[] = {0 ,1 ,2 }; int b[] = {2 ,2 ,2 }; int * mul = matrix_multiple (a,b); cout<<"Result:" ; for (int i=0 ;i<3 ;i++) { cout<<mul[i]<<"," ; } cout<<"\n" ; }
输出:
1 Result:-858993460,-858993460,-858993460,
这是因为res变量的作用域仅在函数中,执行完函数后res会被销毁,再次访问属于危险操作。
一般可以这样修改:
1 2 3 4 5 6 7 8 int * matrix_multiple (int a[3 ], int b[3 ]) { static int res[3 ]; res[0 ] = a[0 ]*b[0 ]; res[1 ] = a[1 ]*b[1 ]; res[2 ] = a[2 ]*b[2 ]; return res; }
输出:
使用动态数组声明res
别忘记手动删除new创建的指针。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 int * matrix_multiple (int a[3 ], int b[3 ]) { int * res = new int [3 ]; res[0 ] = a[0 ]*b[0 ]; res[1 ] = a[1 ]*b[1 ]; res[2 ] = a[2 ]*b[2 ]; return res; } int main (int argc, char * argv[]) { int a[] = {0 ,1 ,2 }; int b[] = {2 ,2 ,2 }; int * mul = matrix_multiple (a,b); cout<<"Result:" ; for (int i=0 ;i<3 ;i++) { cout<<mul[i]<<"," ; } cout<<"\n" ; delete [] mul; }
输出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 void matrix_multiple (int a[3 ], int b[3 ],int *& res) { res[0 ] = a[0 ]*b[0 ]; res[1 ] = a[1 ]*b[1 ]; res[2 ] = a[2 ]*b[2 ]; } int main (int argc, char * argv[]) { int a[] = {0 ,1 ,2 }; int b[] = {2 ,2 ,2 }; int * res = new int [3 ]; matrix_multiple (a,b,res); cout<<"Result:" ; for (int i=0 ;i<3 ;i++) { cout<<res[i]<<"," ; } cout<<"\n" ; delete [] res; }
输出:
指针与函数
传值传参、地址传参与引用传参
在向函数传递参数时,通常是传值传参 。程序会默认复制一份变量,将复制好的变量传入函数中。因此如果函数中对输入做出了修改,输入的变量并不会发生改变。
地址传参 也是传值传参。下面的例子可以看到地址传参传入的地址其实也是复制的,不会和输入产生联系。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 void print_address (int * ptr) { int a = 4 ; ptr = &a; cout<<ptr<< '\n' ; } int main (int argc, char * argv[]) { int a = 10 ; cout<<&a<<"\n" ; print_address (&a); cout<<&a<<"\n" ; return 0 ; }
输出:
1 2 3 000000E2 D88FFD14000000E2 D88FFBF4000000E2 D88FFD14
对传值传参来说,一方面,如果传入的变量类型占用内存较大,可能存在调用复制构造函数用时长,造成的程序效率下降的结果。另一方面,有时我们希望函数对输入的变量进行修改比如swap交换两个变量的值。所以需要用到引用传参 。
函数输入的参数如果在类型右侧加上&,表示引用传值,不会调用复制构造函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 void swap (int & a,int & b) { const int temp = a; a = b; b = temp; } int main (int argc, char * argv[]) { int a = 10 ,b = 40 ; std::cout<<"a:" <<a<<"\t" <<"b:" <<b<<"\n" ; swap (a,b); std::cout<<"a:" <<a<<"\t" <<"b:" <<b<<"\n" ; return 0 ; }
输出:
有时会配合const使用,以避免调用构造函数浪费时间,从而加快运行速度。
1 2 3 4 5 6 7 bool isInCircle (const Ray& ray, const float & t) const { Vector3 p = ray.origin + ray.direction * t; Vector3 offset = diskCenter - p; offset.y = 0.0f ; float d = magnitude (offset); return (d<= radius_pow); }
地址传参与引用传参的区别
地址传参使用指针传递参数,传入的参数是本质是变量 、是可变的 、与输入变量没有关联的 、可空 且无类型检查的 。
引用传参是传递输入变量的别名 ,是不变的 、始终与输入变量有关联 、非空 且有类型检查的 。
函数指针
指针还可以指向函数,调用时使用指向函数的指针即可。
需要区别的是函数指针 和指针函数 ,前者是指向函数的指针,后者是返回指针的函数。
函数指针声明格式如下:函数返回值类型 (<*>+<变量名>)(参数列表);
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 print_my_string_1 () { cout<<"Call My Function1" << '\n' ; } void print_my_string_2 (int a) { cout<<"Call My Function2: " <<a<< '\n' ; } int add (int a,int b) { return a+b; } int main () { void (*func_ptr1)() = print_my_string_1; func_ptr1 (); void (*func_ptr2)(int ) = print_my_string_2; func_ptr2 (4 ); int (*func_ptr3)(int ,int ) = add; int c = func_ptr3 (1 ,2 ); cout<<"Call Add Function: " <<c<< '\n' ; }
输出:
1 2 3 Call My Function1 Call My Function2: 4 Call Add Function: 3
回调函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 void func1 (void (*callback)()) { cout<<"Call Func1" << '\n' ; callback (); } void func2 () { cout<<"Call Func2" << '\n' ; } int main () { void (*callBackTest)() = func2; func1 (callBackTest); }
输出:
内存管理(动态数组)
一维数组
声明一个数组中存储类型的指针,使用new关键字声明一个该类型的数组,使指针指向该数组的头部。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 int main () { int array_length = 0 ; cin>>array_length; int * array_head = new int [array_length]; for (int i=0 ;i<array_length;i++) { cin>>*(array_head+i); } for (int i=0 ;i<array_length;i++) { cout<<"Array " <<i<<" : " <<*(array_head+i)<<"\n" ; } }
输出:
1 2 3 4 5 6 7 3 1 2 3 Array 0 : 1 Array 1 : 2 Array 2 : 3
多维数组
声明一个数组中存储类型的指针,使用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 int main () { int array_const[2 ][3 ] ={{1 ,2 ,3 },{4 ,5 ,6 },}; int array_x = 2 ; int array_y = 3 ; int ** array = new int *[array_x]; for (int i=0 ;i<array_x;i++) { array[i] = new int [array_y]; } for (int x=0 ;x<array_x;x++) { for (int y = 0 ;y<array_y;y++) { cin>>array[x][y]; } } for (int x=0 ;x<array_x;x++) { for (int y = 0 ;y<array_y;y++) { cout<<x<<"," <<y<<" : " <<array[x][y]<<"\n" ; } } }
智能指针
上面的例子中,使用new创建一个指针,使用delete删除该指针。这些操作都需要程序员手动操作,如果程序员忘记删除,就会造成内存泄露 。所以C++设计了智能指针,在适当时刻自动删除指针。
篇幅有点长了,具体的内容见另一篇吧!
C++中的智能指针