C++ 数组指针和指针数组及const细节

概述

最近开始看BJ的《The C++ Programing Language》,感觉良多啊。

首先呢,我觉得要想通读这本书,就必须具备以下两个条件之一:

  1. 将《C++ Primer》通读完,而且掌握了十之八九

  2. 必须要有强大的心理素质,否则你在没基础的情况下,很容易在三章之内崩溃的 ## 问题发现 ## 今天看了 指针的数组 和 数组的指针 , 突然觉得自己好无知啊 先上一个例题:

<1> 写出下面声明:一个到字符的指针 ;一个包含十个整数的数组 ; 一个包含10个整数的数组的引用 ; 一个到字符串的数组的指针 ; 一个到字符指针的指针 ; 一个常量整数 ; 一个到到常量整数的指针 ; 一个到整数的常量指针。 并为每个声明做初始化

解析:

咋一看题,觉得是不是很简单? 可是看看答案……………………

1
2
3
4
5
6
7
8
9
char *pc = 0;
int ai[10] = {1 ,1 , 2 , 3 , 5 , 8 , 13 , 21 , 34 , 55}
//当然 , 如果你把以下两个做对的话,那就不用往下看了…………
int (&rai)[10] = ai;
std::string (*pas)[5] = 0;
char **ppc = &pc;
int const ci = 42;
int const *pci = &ci;
int *const p = ai+3;

这些声明中大部分都是直截了当的。值得一提的是:

1
std::string *aps[5];

这句和上面的 std::string (*pas)[5]; 对比下,aps是一个指向字符串的指针的数组/数组在指针之前考虑/ ,

而pas是一个指向字符串的数组的指针/指针在数组之前考虑/

当然,这样要记住声明运算符的确切优先级 是一件很蛋疼的事,所以,有人开始用 typedef 来声明构造出所需要的 类型 , 所以针对题中 “一个包含10个整数的数组的引用” , “一个到字符串的数组的指针“有了下面的解:

1
2
3
4
typedef int Array10Int[10];
Array10Int &rai = ai;
typedef std::string Array5String[5];
Array5String *pas = 0;

其实 , 我说的也估计不怎么对,暂时写下自己的心得吧:

1
2
3
int (&rai)[10] = ai;
typedef int Array10Int[10];
Array10Int &rai = ai;

两个对比下,typedef 所做的事就是自定义了一个内置类型数组名,即: typedef 类型名 数组名[str_length] ,

而后我们可以用这个自定义的数组名来定义 引用 或者 指针 ……………… 说的不是废话嘛 , 这是个人都能总结出来的~~好吧,

其实我不懂,不过百度有篇文章,不错哦,看完后就会觉得有种醍醐灌顶的感觉了,废话不说,看文章:

==========又一个华丽丽的分割线======================

转自百度第一个搜索条…… 指向数组的指针(叫做数组指针)
指向函数的指针,即函数指针
int *f(int),f函数,返回int型的指针
int (*f)(),f是指向int(int)的指针,f是一个函数指针变量

函数名是一个地址,一个函数在编译时被分配一个入口地址,将这个入口地址称为函数的指针,可typedef似乎很简单,如typedef int integer;然而,这些简单的typedef语句容易让人产生一种误解,typedef就是一种宏替换, 把后面的自定义类型替换成前面的已知类型,事实是这样的吗?显然不是!

考虑这样的问题:如何定义一个指向整型的指针类型?如何定义一个函数指针类型?

第一个问题很简单:typedef int* int_pointer;即可,对于第二个问题,似乎就没有那么简单了,

首先,看函数指针的定义方法:int (*p)(const&, int); 这个p指向的函数必须返回int,形参必须是const&int

现在要将这种指针类型命名为func_pointer,其定义的方法如下:

1
typedef int (*func_pointer)(const&, int);

可以这样来理解:typedef int integer;typedef去掉,那就是个变量的定义,这儿即定义了一个int型的变量integer,考虑这个integer是什么类型的,那么这个typedef语句就是将integer定义为这个类型的。

typedef int (*func_pointer)(const&, int);中的typedef去掉,就成了一个函数指针定义,即func_pointer被定义为函数指针类型变量,那么原来的typedef即将func_pointer定义为函数指针类型。

typedef int (*func_pointer)(const&, int);
typedef是一种存储类型的声明。
函数指针类型,是一种类型,int (*)(const&, int);就是函数指针类型,
func_pointer是函数指针类型的变量,是一个变量的名字。

int (*testCases[10])();这个表达式是什么意思?————-函数指针数组

函数指针类型。int(*)()
变量名是testCases[10],是一个函数指针类型的变量,—-函数指针类型的数组
指数组testCase,大小是10,其各个元素都是函数指针类型的。
等价于:
typedef来简化这种定义:
typedef int (*PFV)();
PFV testCases[10];

数组指针(即指向数组的指针)的定义: int (*ptr)[3];
这个表达式定义了一个数组指针ptr,ptr一次跨越一个由3个int型组成的一维数组。发现其定义的方式与函数指针定义的方式很相似,只是把()换作了[]。

指针,数组,函数糅合在了一起问题变得复杂起来。它定义了数组,testCases[10],数组中的元素是函数指针,函数指针的类型是 int (*)();

怎么来理解这种定义呢?首先考虑数组的定义,数组的定义一般模式是:类型 数组名[大小];

考虑这个表达式,似乎是定义了一个数组,但是数组名[大小]被夹在了中间,那么类型是什么呢,发现类型并不是简单的数据类型,
而是一个函数指针类型int (*p)(),这个函数没有参数,返回int型。从而这个表达式的含义是:定义了一个函数指针型的数组,大小是10。
可以利用typedef来简化这种定义:

1
2
typedef int (*PFV)();
PFV testCases[10];

其实int (*testCases[10])();这儿我们定义了一个函数指针数组,数组是主体。

下面考虑这样的问题:如何定义一个指向数组的指针(叫做数组指针)?

指向数组的指针,好像比较新鲜,所谓指向数组的指针,即指针的一步跨越是一个数组,跟指向整型的指针一步跨越一个整型一个道理。

事实上前面已经碰到了指向数组的指针,如二维数组名,实际上就是一个指向数组的指针,它一次跨越一行的数据,实际上即是跨越了一个一维数组,
而三维数组名呢,也是一个指向数组的指针,它一次跨越的是低维组成的一个二维数组。

数组指针(即指向数组的指针)的定义:

1
int (*ptr)[3];

这个表达式定义了一个数组指针ptr,ptr一次跨越一个由3个int型组成的一维数组。
发现其定义的方式与函数指针定义的方式很相似,只是把()换作了[]。
更进一步,如果要定义一个指向数组的指针,而数组中的元素不是简单的int型,而是比较复杂的类型,那该如何定义呢?
事实上数组指针这种东西就已经够稀有的了,一般编程绝对不会用到,我们只需要能读懂一些比较复杂的东西就行了,自己没有必要构造这么复杂的类型。

例题

比较复杂的表达式:

1、int (*(*(*p())[])())[];

首先,根据p()判断p是一个函数,再根据p()前面的*号判断该函数返回一个指针,下面就看这个指针指向的是什么类型了,

我们可以把*p()替换成一个*pointer,这个pointer就是函数p返回的指针,那么就成了int (*(*(*pointer)[])())[];

再根据(*pointer)[],这说明了指针pointer是指向的一个数组,那么这个数组中的元素是什么类型呢?

由于数组名实际上就是个指针,我们把(*pointer)[](即(*p())[])替换成一个array,这样就成了 int (*(*array)())[];

发现array是一个函数指针,从而数组中的每个元素是函数指针,而这个函数呢,又返回一个指针类型,把(*array)()用func代替,就成了int (*func)[];

这说明了func函数返回的是指向数组的指针,数组中的元素是int型。

这个表达式够酷!!!

2、p = (int( * (*)[20])[10])q;

这是一个强制类型转换,q被强制类型转换成一个这样的指针类型,

这个指针呢直线一个具有20个元素的数组,这个数组中的元素也是指针,是指向另外一种数组,这种数组是含有10个int型数据的一维数组。

总结:

可见,分析复杂的表达式时(所谓复杂,即糅合了指针,数组,函数三样,缺少了一样就不会复杂了),

从括号的最里层做起,最里层的东西是复杂表达式的“根节点”,然后一层一层脱,脱的时候,是这样的,

比如里层是个数组,那么就是说这个数组的元素是什么呢,那就是外层的东西,如果里层是个有返回值的函数,那么就是说这个函数返回什么值呢?

那就是外层的东西,就这样一层一层地把表达式解析清楚。

关于typedef还有一些要说的:

typedef int (*PFV)(); 这是定义了一个函数指针,那么PFV p;就可以定义了一个指向函数的指针。

typedef int (*p[10])(); 这是把p定义为函数指针数组,那么 p array;语句就可以定义了一个函数指针数组,数组名即为array,array这个数组含10个元素。

typedef int (*parray)[3];这是定义了一个指向整型数组的指针,那么 parray ptr;就定义了一个指向数组的指针。

如何对这个ptr赋值或者初始化呢?

事实上,是通过二维数组名来对其进行赋值(初始化)的,因为二维数组名作为指针来讲,就是一个指向数组的指针,一次跨越一个数组。

typedef int a[3][3]; 这个语句什么意思呢?这是把a定义为一个3*3 的整型数组类型。当a b = {1}时就完成了一个3×3的整型数组的定义初始化的工作。

同样,简单一点 typedef int a[3];这个语句是把a定义为一个一维数组类型。

typedef void func(int); 这个语句定义了一个函数类型。通过这个typedef,我们可以比较清晰地定义出函数指针,func* p;即可。(p是void(int)类型指针)

1
2
typedef char* string;
const string str;

这个str是什么类型的呢?const char * str,即指向常量的指针类型?

事实上,答案有些不可思议,str是一个常量指针,而不是指针常量,

const修饰符针对的是指针(就是strstring是一个指针类型,指向char类型变量),而不是char

再看一道练习题:

<2> 用typedef去定义类型 unsigned char ; const unsigned char ; 到整数的指针 ; 到char的指针的指针 ; 到char的数组的指针 ;7 个到int 的指针的数组 ; 到包含7个到int的指针的数组的指针 ; 包含8个数组的数组,其中每个数组包含7个到int的指针。

答案是:(最好自己先写一下)

1
2
3
4
5
6
7
8
typedef unsigned char UnsignedChar;
typedef UnsignedChar const ConstUnsignedChar ;
typedef int* IntegerPointer ;
typedef char** PointPointChar ;
typedef char* PointArrayChar;
typedef IntegerPointer Array7IntegerPointer[7] ;
typedef Array7IntegerPointer* PointerArray7IntegerPointer;
typedef IntegerPointer Array[8][7] ;

还有一个,很多初学者对字符串的长度和大小不太敏感 , 这一认识是错误的,

示例:

1
char str[] = "a short string";

大小是: 字符的个数 , 15
长度是: strlen(str); 答案是14

还有就是关于const 的一些细节

今天感触最深的是const引用作为函数形参了 , 第一遍学的时候就没怎么注意,唉…… 老办法,先拿一个程序开刀:

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
#include <iostream>
using namespace std;
void f(char c)
{
cout<< c<< endl;
}
void g(char& c)
{
cout<< c<< endl;
}
void h(const char& c)
{
cout<< c << endl;
}
int main()
{
char c;
unsigned char uc ;
signed char sc;
f('a');
f(49);
f(3300); //可以,但可能有危险
f(c);
f(uc);
f(sc);
g('a'); //错误
g(49);// error
g(3300);//error
g(c);
g(uc);// error unsigned char -> char 产生右值
g(sc);// error signed char -> char 产生右值
h('a');//临时对象
h(49);//临时对象
h(3300);//临时对象 , 可能有危险
h(c);
h(uc);//临时对象
h(sc);//临时对象
return 0;
}

f()函数就什么都不说了,肯定是类型的隐式转换啦
g()函数的形参是 char& 那么将已知的常量如 ‘a’ , 49 , 3300 传入的时候发生错误,因为不能把 const(常量) 赋给非 const
可是传入 su ,uc 的时候为什么错了? 看注释。
当然,为什么说 unsigned charsigned char 为什么转化为char时候变成右值呢?
因为这两种类型之一必然会表示某些普通 char 类型无法表示的值(不太令人信服吧,可人家答案这么说的 ,木办法,我也不懂,以后知道了我会更新的)

也就是说,当非const的引用 作为函数形参时 , 务必保证不要将右值(不可以被改变的值)传入函数,否则会引起错误

h() 函数的形参是 const char&

当右值被用过引用传递给const类型时可能引进临时对象。这种临时对象可能是需要的,因为被传入引用的那个函数可能需要取得

被引用对象的地址。由于文字量(字面值)没有地址 , 因此必须首先将它们装入一个临时对象,这种对象是有地址的。

/当然,建立这种临时对象可能会导致严重的性能恶化。因此,熟悉什么情况下回出现临时对象是很有价值的/

哎呀,有一点忘说啦:

函数形参为const char& 时,只有传入类型与char 不一致时, 才会产生临时对象{当然啦,这里不一定是char,其他类型也是可以的}

热评文章