指针

如果指针在定义的时候不初始化那可能指向任意的内存地址值,这样的指针被称为 野指针,指向一个未知的内存位置

1
2
long *fellow;
*fellow = 223323;

上面的代码中 fellow 没有初始化,所以这种错误可能存在 bug,所以在对指针应用解引用操作符前,需要分配一个确定的适当的地址

1
2
3
4
int *pt;
pt = 0xB8000000; // type mismatch
pt = (int *) 0xB8000000; // types now match
cout << pt << endl; // 输出 0xB8000000

C++ 不能直接赋值地址,需要通过强制类型转换,将数字转换为适当的地址类型

空指针

与野指针对应的就是 空指针,表示不指向任何有效的内存地址

1
2
3
4
5
// 空指针
int *p1 = nullptr; // C++ 一般用这个
int *p2 = 0;
// must #include cstdlib,NULL 在头文件中被定义为 0
int *p3 = NULL; // C 语言空指针,is equivalent to initializing it to 0

void 指针

没有具体的指向类型,可以用来存储任何类型的指针,并且可以在需要时转换为其他指针类型

1
2
3
double obj  = 3.14;

void *pv = &obj;

void 指针不能解引用

引用

& 一般表示变量的地址,但 C++ 赋予了另一个含义,用来声明引用

引用是已定义变量的别名

1
2
int rats;
int &rodents = rates;

引用变量 rodents 和变量 rates 指向相同的值和内存单元

引用变量在创建时必须初始化,而且一旦和某个变量关联就不能进行修改

1
2
3
4
5
6
7
8
9
10
11
// pt 改为指向 bunnies,但是 rodents 引用的还是 rats
int rats = 75;
int *pt = &rats;
int &rodents = *pt;
int bunnies = 75;
pt = &bunnies;

int p0 = 0;
int p1 = 1;
int &p2 = p0;
p2 = p1; // 此时不是将 p2 绑定到 p1,而是 p0 的值修改成了 p1 的值

将引用变量用作参数,函数将使用原始数据,而不是其副本,所以引用经常被用作函数参数,可以 避免拷贝开销

指针引用

1
2
3
4
5
6
7
// r 和 p 指向同一个地址
int i = 42
int *p;
int *&r = p;

r = &i;
*r = 0;

当类型不匹配时,C++ 会生成临时变量

1
2
3
4
5
6
7
double dval = 3.14;
const int &ri = dval;

// 等效于
double dval = 3.14;
const int temp = dval;
const int &ri = temp;

当实参和引用参数不匹配,C++ 将生成临时变量,仅当参数为 const 引用时才允许这么做,允许条件:

  • 实参类型正确,但不是左值
  • 实参类型不正确,但可以转换为正确的类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>

using namespace std;

double recube(const double &ra);

int main ()
{
cout << recube(5.0) << " = cube of " << 5.0 << endl;
return 0;
}
// 5.0 会传给一个临时变量,此时 ra 是临时变量的引用
double recube(const double &ra)
{
return ra * ra * ra;
}

尽可能使用 const,将引用参数声明为常量数据的引用的理由有三个:

  • 使用 const 可以避免无意中修改数据的变成错误
  • 使用 const 使函数能够处理 const 和 非 const 实参,否则将只能接受非 const 数据
  • 使用 const 引用使函数能够正确生成并使用临时变量

返回引用

1
2
3
4
5
6
7
8
9
// 假设现在有个结构体类型是 free_throws

free_throws &accumulate(free_throws &target, const free_throws &source)
{
...
return target;
}

dup = accumlate(team, five);

如果不返回引用,则会将返回值复制到一个临时位置,然后再复制给 dup,但是返回引用可以直接将内容赋值给 dup

但是下面的写法错误

1
2
3
4
5
6
const free_throws &clone(free_throws &ft)
{
free_throws newguy;
newguy = fr;
return newguy
}

因为函数返回指向一个指向临时变量的引用,函数运行完毕后它将不存在

1
2
3
4
5
6
const free_throws &clone(free_throws &ft)
{
free_throws *pt;
*pt = ft;
return *pt;
}

使用指针是可行的,虽然运行完后指针不存在了,但是指针里面存在的内容仍然存在

所以适合的方法是返回作为参数传递给函数的引用,或者用 new 分配新的内存空间

基类引用可以指向派生类对象,而无需进行强制类型转换

建议:

  • 数据对象是内置数据类型,则使用指针
  • 数据对象是数组,只能使用指针
  • 数据对象是结构,则使用引用或指针
  • 数据对象是类,则使用引用

函数指针

假设要设计一个名为 estimate() 的函数,估算编写指定行数的代码所需的时间,并且希望不同的程序员都将使用该函数。对于所有用户来说,estimate() 中一部分代码都是相同的,但该函数允许每个程序员提供自己的算法来估算时间,因此可以通过函数指针来完成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 假如有个函数
double pf(int);

// 则正确的指针类型声明如下
double (*pf)(int);

// 函数地址赋值
double pam(int);
pf = pam;

// 使用指针来调用函数
double x = pam(4); // using function name
double y = (*pf)(5); // using pointer
double z = pf(6); // pf 和 (*pf) 是等价的

深入探讨函数指针

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
// 假设现在有 f1,f2 和 f3 3个函数
// or const double *f1(const double ar[], int n);
const double *f1(const double *ar, int n);
const double *f2(const double ar[], int n);
const double *f3(const double ar[], int n);

int main()
{
double av[3] = {1112.3, 1542.6, 2227.9};
// part1:
// p1(p2): pointer to a function,定义一个函数指针
const double *(*p1)(const double *, int) = f1;
auto p2 = f2;

cout << "Adress Value" << endl;
cout << (*p1)(av, 3) << ": " << *(*p1)(av, 3) << endl; // 调用
// (*p2) 和 p2 等价
cout << p2(av, 3) << ": " << *p2(av, 3) << endl;

// part2:
// pa(pb) is an array pointers
const double *(*pa[3])(const double *, int) = {f1, f2, f3};
auto pb = pa;
for (int i = 0; i < 3; i++)
cout << pa[i](av, 3) << ": " << *pa[i](av, 3) << endl;
// or cout << (*pa[i])(av, 3) << ": "
// << *(*pa[i])(av, 3) << endl;
// part3
// const double *(*(*pc)[3])(const double *, int) = &pa;
// 最里面的 * 表示 pc 是一个指针,指向 3 个元素组成的数组
// 第二个 * 表示数组里每个元素都是指针
// 最外面的 * 表示每个都是函数指针
auto pc = &pa; // pc 是指向 pa 数组的指针
// const double *(*(*pd)[3])(const double *, int) = &pa;
cout << (*pc)[0](av, 3) << ": " << *(*pc)[0](av, 3) << endl;
// const double *pdb = (*pd)[1](av, 3);
// cout << pdb << ": " << *pdb << endl;
}

const double *f1(const double *ar, int n)
{
return ar;
}

const double *f2(const double ar[], int n)
{
return ar + 1;
}

const double *f3(const double ar[], int n)
{
return ar + 3;
}