blueyi's notes

Follow Excellence,Success will chase you!

0%

举例详解一维数组、二维数组与指针、函数的问题

  首先需要清楚数组名是数组首元素的地址,不管是一维还是多维,它是个常量,而指针是个可以存储地址的变量。使用时可以将数组名看成一个常量指针。
  C/C++中没有内置的多维数组,多维数组是数组的数组。例如二维数组就是元素为一维数组的一维数组,此时数组名依然是数组的第一个元素的地址。
具体解释看代码注释及代码的运行结果。
  C/C++中的函数无法返回数组,所以在使用函数操作数组时,只能通过指针或者结构体等方式来间接返回数组,当然可以返回数组名,因为数组是个地址,相当于一个常量指针。具体详解可以看SOF: Return a 2d array from a function

以下代码为了方便查看和分析,所以只是代码片段

一维数组与指针

1
2
3
4
5
6
int ar[20] = {1,2,3};  //前3个元素分别是1,2,3
int *p1 = ar; //将p1指向ar数组的第一个元素的地址,指向的是数组的元素,p1+1的增量为1个元素所占的字节空间
int (*p2)[20] = &ar; //将p2指向ar数组的地址,即指向整个数组,p2+1的增量为&ar所代表的数组所占的字节空间
std::cout << "p1+1: " << p1+1 << "\tar+1: " << ar+1 << "\t&ar[1]: " << &ar[1] << std::endl;
std::cout << "*(p1+1): " << *(p1+1) << "\t*(ar+1): " << *(ar+1) << std::endl;
std::cout << "*p2: " << *p2 << "\tp1: " << p1 << "\t**p2: " << **p2 << "\t*p1: " << *p1 << std::endl;

p1是一个指向int型变量的指针,p1存储的地址与ar是一样的,都是数组第一个元素的地址,但p1是指针变量,ar是个常量地址,p1++等价于p1=p1+1, 但不能使用ar++。下标的操作在C++中会转化为对地址的操作,如ar[i]会被转化为(ar + i), p2是一个指针,不同于int *p2[20];不加小括号时,p2会优先与后面的[20]结合,导致p2成为指针数组, 即含有20个int型指针的数组。而加了小括号p2先与结合,p2成为指针,该指针指向一个含有20个int 型元素的数组,恰好与ar数组类型相同。注意ar是一个数组,数组名表示的是该数组的第一个元素的地址。 例外情况是对数组名使用sizeof运算符将获得整个数组的大小(单位是字节)。 而对数组名取地址时,得到的将是该连续20个地址空间的首地址,虽然值相同,但意义不同。 &ar[0]/p1/ar表示的是一个4字节的内存块的首地址,而&ar却表示204个字节的内存块首地址。 相当于此时对p1/ar加1时增加的最小单位是4个字节的连续内存空间,也就是对应第二个元素,而对&ar/p2加1是毫无意义的,增加是204个字节的连续内存空间,将会越界, 可对其解引用一次得到数组第一个元素的地址,即*p2等价于p1。

二维数组与指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
std::cout << "**********二维数组与指针*********" << std::endl;
//首先C/C++中没有内置的二维数组,多维数组是都是指数组的数组,使用时很多情况下可以视为二维数组
int arr[5][6] = {{1,2,3}}; //5行6列的数组,实际是一个含有5个元素的一维数组,每个元素又是一个含有6个int值的数组
int (*pp1)[6] = arr; //pp1是一个指向含有6个int元素的数组的指针
int (*pp2)[5][6] = &arr; //pp2是一个指向含有5行6列的二维数组的地址,即指向的是整个数组的数组


std::cout << "&arr[0][0]: " << &arr[0][0] << "\tarr: " << &arr << "\tpp1: " << pp1
<< "\t*pp2: " << *pp2 << "\t**arr: " << **arr << std::endl;

//使用指针访问二维数组
for (int (*pr)[6] = arr; pr != arr + 5; ++pr) {
for (int *pc = *pr; pc != *pr + 6; ++pc)
std::cout << *pc << " ";
std::cout << std::endl;
}

二维数组arr[i][j]会被解析成((arr + i) + j),arr依然是第一个元素的地址, 此处的第一个元素即可以表示arr[0][0](即二维数组的第一个元素),也可以表示以行为元素的一维数组的 第一个元素(arr + 0)(即第一行的6个元素),显然这两个地址是相同的。所以对arr增加1,即移到下一个 元素的地址,下一个元素即第二行。arr的元素是含有6个int的一维数组,所以指向arr的指针类型也必须是含有 6个int的一维数组,所以pp1的括号和后面的[6]必须要有。 pp2的解释与p2类似

函数和数组

首先明确C/C++中函数无法返回数组,所以对数组的操作和返回只能通过指针,而数组名本身就是个地址,可以看成是个常量指针
以下两种方式定义一个返回二维数组的函数,实际上返回的依然是指针

1
2
3
4
5
6
7
8
//使用typedef定义一个类型别名,该类型为一个指针,该指针指向含有6个int元素的数组
//小括号不能省略,否则将成为含有6个指针的数组。由于二维数组就是数组的数组,所以该函数可以
//接受一个列数为6的二维数组作为参数,并返回一个指针,该指针同样指向含有6个int元素的数组
typedef int (*Tarr)[6];
Tarr func(int (*arr)[6])
{
return arr;
}

上述声明等价于

1
2
3
4
5
6
7
8
int (*func(int (*arr)[6]))[6]
{
return 0;
}
int c[5][6];
Tarr b = func(c);
std::cout << "&b[1][1]: " << &b[1][1] << "\t&c[1][1]: " << &c[1][1] << std::endl;

函数和指针

这里的介绍非常详细http://www.cnblogs.com/TenosDoIt/p/3164081.html
C语言函数指针的定义形式:返回类型 (*函数指针名称)(参数类型1, 参数类型2, 参数类型3, ...);
C++语言函数指针的定义形式:返回类型 (类名称::*函数指针名称)(参数类型1, 参数类型2, 参数类型3, ...);
注意,当定义指向类的成员函数的函数指针时,该成员函数如果不是静态成员函数,也必须通过类对象来调用。不能定义指向一个类对象的函数的函数指针。
与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
#include <iostream>

void tf(char c, int i)
{
std::cout << "Normal: " << c << i << std::endl;
}

class A {
public:
void func1(char c, int i)
{
std::cout << "Class func1: " << c << i << std::endl;
}

void func2(char c, int i)
{
std::cout << "Class func2: " << c << i << std::endl;
}

static void func3(char c, int i)
{
std::cout << "Class func2: " << c << i << std::endl;
}

void func4(char c, int i) const
{
std::cout << "Class func2: " << c << i << std::endl;
}
};

int main(void)
{
void (*pf)(char, int) = NULL; //普通函数指针定义并使其指向NULL
pf = tf;
pf('t', 1);

void (A::*pf1)(char, int) = &A::func1; //指向类的非静态成员函数的指针,初始时必须使用取地址符
A obj_a;
(obj_a.*pf1)('f', 1); //这里必须要使用括号把前面对象和函数指针名括住,且必须使用.*,而不是->
// pf = obj_a.func1; //错误,不能让函数指针pf指向对象的指针,即使他们参数和返回值一样

A obj_b;
pf1 = &A::func2;
(obj_b.*pf1)('f', 1);

pf = A::func3; //静态成员可以像普通函数一样不加取地址符
pf('f', 3); //因为是静态成员,所以不需要对象,可以直接通过函数指针调用

void (A::*pfc)(char, int) const = &A::func4; //const 函数的函数指针也必须带有const
A obj_c;
(obj_c.*pfc)('c', 4);
return 0;
}

输出结果:

1
2
3
4
5
Normal: t1
Class func1: f1
Class func2: f1
Class func2: f3
Class func2: c4

函数指针作为参数和返回值

要善用typedef简化函数指针类型的定义。具体看下面的一个例子:

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
#include <iostream>

int display(char c, int i)
{
for (int n = 0; n < i; ++n)
std::cout << c;
std::cout << std::endl;
return i;
}

//接受一个指向形如display的函数指针,一个char和一个int作为参数的函数
//没有返回值
void dfun(int (*pp)(char, int), char c, int i)
{
pp(c, i);
}

//该函数与dfun函数类似,只是其返回值为指向形如display函数的函数指针
int (*pdfun(int (*pd)(char, int), char c, int i))(char, int)
{
pd(c, i);
return pd;
}

int main(void)
{
dfun(display, 'd', 6); //调用带有函数指针作为参数的函数

int (*pd)(char, int) = pdfun(display, 'p', 7); //调用返回值为函数指针的函数
pd('p', 8); //调用函数指针的返回值的函数

//使用typedef简化函数指针类型的定义
//定义一个指向形如display的函数指针的类型pd_type
typedef int (*pd_type)(char, int);
pd_type pdd = display;
pdd('s', 10);

//定义一个指向形如pdfun的函数指针的类型pdfun_tye类型
typedef int (*(*pdfun_type)(pd_type, char, int))(char, int);
pdfun_type ppdfun = pdfun;
pd_type ppd = ppdfun(pdd, 'u', 12); //调用并获取其返回的函数指针
ppd('x', 15);

return 0;
}

输出:

1
2
3
4
5
6
dddddd
ppppppp
pppppppp
ssssssssss
uuuuuuuuuuuu
xxxxxxxxxxxxxxx

函数指针数组

举例说明:

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
#include <iostream>

float f1(float a, int b)
{
std::cout << a << " + " << b << " = " << a + b << std::endl;
return a + b;
}

float f2(float a, int b)
{
std::cout << a << " * " << b << " = " << a * b << std::endl;
return a * b;
}

int main(void)
{
//定义并初始化函数指针数组
float (*pfa[2])(float, int) = {f1, f2};
//调用该函数指针数组
for (int i = 0; i < 2; ++i) {
pfa[i](3, 9);
}

//使用typedef简化定义
typedef float (*f_type)(float, int);
f_type pfa2[] = {f1, f2};
//调用该函数指针数组
for (int i = 0; i < 2; ++i) {
pfa2[i](6, 8);
}

return 0;
}

输出为:

1
2
3
4
3 + 9 = 12
3 * 9 = 27
6 + 8 = 14
6 * 8 = 48

Welcome to my other publishing channels