什么是指针

指针表示存储某一对象(通常是变量、函数等)的内存地址。


普通指针

指针与变量

符号&表示引用,变量名左侧的&表示取该变量的地址。
符号表示解引用,变量名左侧的表示取该变量的值。
声明一个变量在类型名右侧用*表示声明该类型的指针

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; // 指针pc保存变量c的内存地址
cout << "pc持有的指针地址(pc): "<< pc << endl;
cout << "地址指针pc持有的值(*pc): " << *pc << endl << endl;

c = 11; // 内存地址&c中的值从5更改为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): 000000D3BDB3F604
Value of c (c): 5

pc持有的指针地址(pc): 000000D3BDB3F604
地址指针pc持有的值(*pc): 5

地址指针pc持有(pc): 000000D3BDB3F604
地址指针pc持有的值(*pc): 11

Address of c (&c): 000000D3BDB3F604
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: 000000E1082FFB78
&a address: 000000E1082FFB78
&a[0] address: 000000E1082FFB78

a[3] value: 3
a+3 address: 000000E1082FFB84
&a[5] address: 000000E1082FFB8C
a[5] value: 5
&a[6] address: 000000E1082FFB90
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: 0000009C1993F528
&a[0] address: 0000009C1993F528
&a[0][0] address: 0000009C1993F528

数组与函数

下面是一段错误代码,结果并不符合预期

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会被销毁,再次访问属于危险操作。

一般可以这样修改:

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

输出:

1
Result:0,2,4,
  • 使用动态数组声明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
Result:0,2,4,
  • 使用引用传参
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
Result:0,2,4,

指针与函数

传值传参、地址传参与引用传参

  • 传值传参

在向函数传递参数时,通常是传值传参。程序会默认复制一份变量,将复制好的变量传入函数中。因此如果函数中对输入做出了修改,输入的变量并不会发生改变。

  • 地址传参

地址传参也是传值传参。下面的例子可以看到地址传参传入的地址其实也是复制的,不会和输入产生联系。

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
000000E2D88FFD14
000000E2D88FFBF4
000000E2D88FFD14

对传值传参来说,一方面,如果传入的变量类型占用内存较大,可能存在调用复制构造函数用时长,造成的程序效率下降的结果。另一方面,有时我们希望函数对输入的变量进行修改比如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;
}

输出:

1
2
a:10	b:40
a:40 b:10

有时会配合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);
}

输出:

1
2
Call Func1
Call Func2

内存管理(动态数组)

一维数组

声明一个数组中存储类型的指针,使用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++中的智能指针