# 数组与指针的艺术--深入探索c/c++数组与指针的奥秘

114 篇文章 0 订阅
11 篇文章 0 订阅

http://topic.csdn.net/u/20091123/11/0c03d2e2-0655-4634-8287-0e2315d889fc.html?8345

前 言

指针是C/C++的灵魂！它是C/C++众多引人入胜的特性中的一朵奇葩。与底层操作的亲密接触是指针与生俱来的优点，利用指针可以写出许多短小精悍、效率极高的代码。它是C/C++一把无可替代的利器，凭着这把利器，C/C++与其它高级语言相比至少在效率方面胜人一筹。

本文的目的，是希望通过跟各位朋友一起讨论关于数组与指针的几个关键概念及常见问题，加深对数组与指针的理解。笔者不敢奢望能够完全解开你心中的魔结，但如果通过阅读本文，能够让你在日后的数组与指针使用过程中减少失误，笔者就心满意足了。

如果你有不同的意见，欢迎你在评论里留下自己的见解，笔者很乐意跟你一起讨论，共同进步。

如果你觉得我说的全都是废话，那么恭喜你，你的指针已经毕业了。

如果你有太多不明白的地方，那么我介绍你先找一些关于数组与指针的读物看看，笔者推荐你阅读一本叫《C与指针》的书，看完后再回来继续思考你的问题。

“一维数组是一级指针”

“二维数组是二级指针”

“数组名是一个常量指针”

“数组名是一个指针常量”

........................

6.2.5 Types

A pointer type may be derived from a function type, an object type, or anincomplete type, called the referenced type. A pointer type describes an objectwhose value provides a reference to an entity of the referenced type. A pointertype derived from the referenced type T is sometimes called ‘‘pointer to T’’.The construction of a pointer type from a referenced type is called ‘‘pointertype derivation’’.

6.5.3.2 Address and indirection operators

Semantics

The unary & operator returns the address of its operand. If theoperand has type “type”, the result has type “pointer to type”.......Otherwise, the result is a pointer to the object or function designated by itsoperand.

C++中，由于引入了OOP，增加了一种也称为“指针”的实体：类非静态成员指针，虽然也叫指针，但它却不是一般意义上的指针。C++标准是这样说的：

3.9.2 Compound types

....... Except for pointers to static members, text referring to“pointers” does not apply to pointers to members..........

6.2.5 Types

An array type describes a contiguously allocated nonempty set of objectswith a particular member object type, called the element type. Array types arecharacterized by their element type and by the number of elements in the array.An array type is said to be derived from its element type, and if its elementtype is T, the array type is sometimes called “array of T”. The construction ofan array type from an element type is called “array type derivation”.

int a[10];

a的数组类型描述就是int[10]

C89/90的内容：

6.2.2.1 Lvalues and function designators

Except when it is the operand of the sizeof operator or the unary &operator, or is a character string literal used to initialize an array ofcharacter type. or is a wide string literal used to initialize an array withelement type compatible with wchar-t, an lvalue that has type “array of type”is converted to an expression that has type “pointer to type” that points tothe initial element of the array object and is not an lvalue.

C99的内容：

6.3.2.1 Lvalues, arrays, and function designators

Except when it is the operand of the sizeof operator or the unary &operator, or is a string literal used to initialize an array, an expressionthat has type “array of type” is converted to an expression with type “pointerto type” that points to the initial element of the array object and is not anlvalue. If the array object has register storage class, the behavior isundefined.

C89/90规定：

an lvalue that has type “array of type” is......

C99却规定：

an expression that has type “array of tye” is.......

C99中去掉了lvalue的词藻，为什么？我们知道，数组名是一个不可修改的左值，但实际上，也存在右值数组。在C中，一个左值是具有对象类型或非void不完整类型的表达式，C的左值表达式排除了函数和函数调用，而C++因为增加了引用类型，因此返回引用的函数调用也属于左值表达式，就是说，非引用返回的函数调用都是右值，如果函数非引用返回中包含数组，情况会怎样？考虑下面的代码：

#include<stdio.h>

struct Test

{

int a[10];

};

struct Test fun(struct Test* );

int main( void )

{

struct Test T;

int *p = fun( &T).a;                        /* A */

int (*q)[10] = &fun( &T).a;                 /* B */

printf( "%d", sizeof( fun( &T ).a ));       /* C*/

return 0;

}

struct Test fun(struct Test *T )

{

return *T;

}

An lvalue or rvalue of type “array of N T” or “array of unknown bound ofT” can be converted to an rvalue of type “pointer to T.” The result is apointer to the first element of the array.

int a[10], *p = a,*q;

q = a + 1;

q = p + 1;

int main( void )

{

static int a[10], b[10];

int c[10], d[10];

int* e[] = { a, b };     /* A */

int* f[] = { c, d };     /* B */

return 0;

}

B为什么不能通过编译？是由于自动数组名并不是常量表达式。在C中，常量表达式必须是编译期的，只在运行期不变的实体不是常量表达式，请看标准的摘录：

6.6 Constant expressions

A constant expression can be evaluated during translation rather thanruntime, and accordingly may be used in any place that a constant may be.

cd是自动数组，首地址在编译期是不可知的，因为这样的对象在编译期还不存在；ab是静态数组，静态对象从程序开始时就已存在，因此ab的首地址在编译期是已知的，它们都属于常量表达式中的地址常量表达式。

C/C++的数组不同于VB等语言的数组，是有层次的，这个层次指的不是维度，而是象俄罗斯有名的套娃一样，一维套一维，亦即数组的嵌套，数组的元素也是数组，VB等语言的数组与之相比更像一个平面。

6.5.4.2 Array declarators

Semantics

If, in the declaration “T Dl.”Dl has the form

D [ constant expressionopt ]

T D[M]

T[N] D[M]

T D[M][N]

T a[M][N]

a 表达式中的a的类型转换为T ( * )[N]，代表数组的首地址；

&a：是一个指向二维数组对象的指针，类型为T ( * )[M][N]。在C标准出现之前，一些早期的实现并不允许&a，因为这些编译器认为此时的a转换为一个右值，而&运算符要求一个左值，因此非法。C标准委员会鉴于对象的概念已经得到了扩展，而且允许&a并没有害处，因此把&运算符作为一个例外写进了数组到指针的转换条款中。这种情况下的a代表数组对象，&a表示对数组对象取地址，因此&a的结果跟a是相同的，但类型不同。

笔者曾经见过某些观点认为，&a才是数组的首地址，不是a。这个观点初看起来似乎很有道理，一个数组对象的引用，不正是首地址吗？但实际上这种论述是不符合标准的，数组到指针的转换条款规定，当产生一个points to the initial elementof the array object的时候，其前提是由array of typepointer to type的转换，但&a的类型属于pointer to array of type，不是pointer to type，因此真正代表数组首地址的是a本身，不是&a

&a[0][0]：这是数组首元素的地址。&a[0][0]常常被误解为数组a的首地址，其实a[0][0]只不过由于位置特殊，其地址值才与a相同，&a[0][0]是一个T类型对象的引用，不是一个数组对象的引用，而且其类型不是由array of type转换得来的，因此其意义不是数组首地址。

a[i]（其中 i >= 0 && i < M）：从数组嵌套的观点来看，a是一个一维数组，元素的类型为数组类型，因此a[i]的类型为T[N]，在表达式中转换为T*，是第i个一维数组的首地址。

a + 1a隐式转换为指针类型T( * )[N]然后加1，请记住指针加法是以指针指向对象的大小为步长的，因此a + 1将跨过N * sizeof( T )个字节。

&a + 1：与a + 1同理，&a类型为T( * )[M][N]，因此&a + 1的步长为M * N * sizeof( T )

6.5.2.1 Array subscripting

Constraints

One of the expressions shall have type ‘‘pointer to object type’’,the other expression shall have integer type, and the result has type ‘‘type’’.

另一个表达式的类型是integer，这意味着表达式的值可以是负数，这是由于指针运算里包含了减法的缘故，但是要注意不应该发生越界的行为。

int a[10]*p = a;

p[0] = 10;

( p + 1 )[0] = 20;

0[p + 1] = 10;

( &a )[0][0] = 20;

0[&a][0] = 30;

0[0[&a]] = 40;

a[0] = “0123456789ABCDEF”[0];

p[0]：就是a[0]

( p + 1 )[0]p移动一个int的距离，就是a[1]

0[p + 1]：就是( p + 1 )[0]

( &a )[0][0]：这个表达式有点古怪，a的类型是int[10]&a就是int( * )[10]，是一个指向具有10int元素的一维数组的指针，(&a )[0]就是&a指向的第0个元素，类型为int[10]，因此( &a )[0][0]就是( &a )[0]的第0个元素。

0[&a][0]：把第一维的0&a调换一下，就是0[&a][0]

0[0[&a]]：再调换0[&a]与第二维[0]中的0，就成了0[0[&a]]，跟( &a )[0][0]等价。

char* convert( unsigned longValue )

{

static char Buffer[sizeof( unsigned long ) * 2 + 1];

int i;

for( i = sizeof( unsigned long ) * 2 - 1; i >= 0; --i )

{

Buffer[i] = "0123456789ABCDEF"[Value % 16];

Value /= 16;

}

return Buffer;

}

Remainder = Value % 16;

if( Remainder >= 10 )Buffer[i] = 'A' + Remainder - 10;

else Buffer[i] = '0' +Remainder;

6.4.4 Constants

Syntax

constant:

integer-constant

floating-constant

enumeration-constant

character-constant

C++的情形有所不同，C++将字符串字面量归入了常量当中：

2.13 Literals

There are several kinds ofliterals.21)

literal:

integer-literal

character-literal

floating-literal

string-literal

boolean-literal

21) The term “literal” generally designates, in thisInternational Standard, those tokens that are called “constants” in ISO C.

2.13.4 String literals

……..An ordinary string literal has type “array of n const char” andstatic storage duration (3.7), where n is the size of the stringas defined below, and is initialized with the given characters.

4.2 Array-to-pointerconversion

A stringliteral (2.13.4) that is not a wide string literal can be converted to anrvalue of type “pointer to char”; a wide string literal can be converted to anrvalue of type “pointer to wchar_t”. In either case, the result is a pointer tothe first element of the array. This conversion is considered only when thereis an explicit appropriate pointer target type, and not when there is a generalneed to convert from an lvalue to an rvalue. [Note: this conversion isdeprecated. See Annex D. ] For the purpose of ranking in overload resolution(13.3.3.1.1), this conversion is considered an array-to-pointer conversionfollowed by a qualification conversion (4.4). [Example: "abc"is converted to “pointer to const char” as an array-to-pointer conversion,

and then to “pointer to char”as a qualification conversion. ]

6.4.5 String literals

It is unspecified whether these arrays are distinct provided theirelements have the appropriate values.

C中，由于字符串字面量不是常量，而且const限定的变量不是常量表达式（C中的常量表达式必须是编译期的），因此所有的常量和常量表达式都是右值。但C++将字符串字面量归入常量，将const限定的变量归入常量表达式，这意味着在C++中存在左值常量和左值常量表达式。

CC++在这方面的差异反映出两者对待常量的不同视角。C认为常量是不应该拥有存储空间的，这是非常传统的观点；而C++把常量的概念延伸到了对象模型，是对对象概念的有益扩展，但同时也带来了一些问题，一个具有对象性质的实体，难以避免存在某些合法或不合法的手段去修改其内容，这种行为常常令常量对象的常量性质处于尴尬的境地，由此也催生了常量折叠这一类巧妙的折中。

const一词在字面上来源于常量constantconst对象在C/C++中是有不同解析的，如第二章所述，在C中常量表达式必须是编译期，运行期的不是常量表达式，因此C中的const不是常量表达式；但在C++中，由于去掉了编译期的限定，因此是常量表达式。

1。这是一个指向常量的指针，简称常量指针；

2。这个指针指向的内容不可改变。

int i = 10;

const int *p = &i;

i = 20;

p指向的对象i明显不是常量，虽然p指向i，但i的值依然可以改变。对于这个现象，C++标准有明确的论述：

7.1.5.1 The cv-qualifiers

a pointer or reference to a cv-qualified type need not actually point orrefer to a cv-qualified object, but it is treated as if it does;

int i = 10, k;

const int *p = &i;

int *q = &i;

i = 20;

*q = 30;

k = *p;

7.1.5.1 The cv-qualifiers

a const-qualified access path cannot be used to modify an object even ifthe object referenced is a non-const object and can be modified through someother access path.

6.7.5.1 Pointer declarators

For two pointer types to be compatible, both shall be identicallyqualified and both shall be pointers to compatible types.

6.2.7 Compatible type andcomposite type

Two types have compatibletype if their types are the same.

6.2.5 Types

The qualified or unqualified versions of a type are distinct types thatbelong to the same type category and have the same representation and alignmentrequirements.

6.5.16.1 Simple assignment

Constraints

One of the following shall hold:

………

— both operands are pointersto qualified or unqualified versions of compatible types,

and the type pointed to by theleft has all the qualifiers of the type pointed to by the

right;

int i = 10;

const int *p =&i;       /* A */

int *q = &i;

const int **p1 =&q;     /* B */

A合法，但B不合法。虽然p1&q都是unqualified的，但p1指向的对象类型为pointer to const int&q指向的类型为pointer to int，如前所述，两者是不相容类型，不符合两操作数必须指向相容类型的规定，因此赋值非法。

int i;

int * const p = &i;

int *q;

q =p;           /* A */

A合法，这种情况并不属于赋值运算符的规则之内，它遵循的是另一个条款：左值转换。一个被限定修饰的左值，在进行左值转换之后，右值具有左值的非限定修饰类型：

6.3.2 Other operands

6.3.2.1 Lvalues, arrays, andfunction designators

Except when it is the operand of the sizeof operator, the unary &operator, the ++ operator, the -- operator, or the leftoperand of the . operator or an assignment operator, an lvalue that doesnot have array type is converted to the value stored in the designated object(and is no longer an lvalue). If the lvalue has qualified type, the valuehas the unqualified version of the type of the lvalue; otherwise, the value hasthe type of the lvalue.

p的值具有p的非限定修饰类型int*，与q类型相容，因此赋值合法。对于C++，基本上与C相同，但有一个例外，就是右值类对象，由于右值类对象仍然是一个对象，C++规定右值类对象具有与左值相同的限定修饰词。

const int * const *** const **const p;

**p = (int *const***)10;

***p=(int*const**)10;

int* ( *( *fun )( int* ) )[10];

C/C++所有复杂的声明结构，都是由各种声明嵌套构成的。如何解读复杂指针声明？右左法则是一个很著名、很有效的方法。不过，右左法则其实并不是C/C++标准里面的内容，它是从C/C++标准的声明规定中归纳出来的方法。C/C++标准的声明规则，是用来解决如何创建声明的，而右左法则是用来解决如何辩识一个声明的，从嵌套的角度看，两者可以说是一个相反的过程。右左法则的英文原文是这样说的：

The right-left rule: Startreading the declaration from the innermost parentheses, go right, and then goleft. When you encounter parentheses, the direction should be reversed. Onceeverything in the parentheses has been parsed, jump out of it. Continue tillthe whole declaration has been parsed.

笔者要对这个法则进行一个小小的修正，应该是从未定义的标识符开始阅读，而不是从括号读起，之所以是未定义的标识符，是因为一个声明里面可能有多个标识符，但未定义的标识符只会有一个。

现在通过一些例子来讨论右左法则的应用，先从最简单的开始，逐步加深：

int (*func)(int *p);

int (*func)(int *p, int(*f)(int*));

func被一对括号包含，且左边有一个*号，说明func是一个指针，跳出括号，右边也有个括号，那么func是一个指向函数的指针，这类函数具有int *int (*)(int*)这样的形参，返回值为int类型。再来看一看func的形参int (*f)(int*)，类似前面的解释，f也是一个函数指针，指向的函数具有int*类型的形参，返回值为int

int (*func[5])(int *p);

func右边是一个[]运算符，说明func是一个具有5个元素的数组，func的左边有一个*，说明func的元素是指针，要注意这里的*不是修饰func的，而是修饰func[5]的，原因是[]运算符优先级比*高，func先跟[]结合，因此*修饰的是func[5]。跳出这个括号，看右边，也是一对圆括号，说明func数组的元素是函数类型的指针，它所指向的函数具有int*类型的形参，返回值类型为int

int (*(*func)[5])(int *p);

func被一个圆括号包含，左边又有一个*，那么func是一个指针，跳出括号，右边是一个[]运算符号，说明func是一个指向数组的指针，现在往左看，左边有一个*号，说明这个数组的元素是指针，再跳出括号，右边又有一个括号，说明这个数组的元素是指向函数的指针。总结一下，就是：func是一个指向数组的指针，这个数组的元素是函数指针，这些指针指向具有int*形参，返回值为int类型的函数。

int (*(*func)(int *p))[5];

func是一个函数指针，这类函数具有int*类型的形参，返回值是指向数组的指针，所指向的数组的元素是具有5int元素的数组。

int func(void) [5];

func是一个返回值为具有5int元素的数组的函数。但C语言的函数返回值不能为数组，这是因为如果允许函数返回值为数组，那么接收这个数组的内容的东西，也必须是一个数组，但C/C++语言的数组名是一个不可修改的左值，它不能直接被另一个数组的内容修改，因此函数返回值不能为数组。

int func[5](void);

func是一个具有5个元素的数组，这个数组的元素都是函数。这也是非法的，因为数组的元素必须是对象，但函数不是对象，不能作为数组的元素。

typedef是一种声明，但它声明的不是变量，也没有创建新类型，而是某种类型的别名。typedef有很大的用途，对一个复杂声明进行分解以增强可读性是其作用之一。例如对于声明：

int (*(*func)(int *p))[5];

typedef  int (*PARA)[5];

typedef PARA (*func)(int *);

typedef的另一个作用，是作为基于对象编程的高层抽象手段。在ADT中，它可以用来在C/C++和现实世界的物件间建立关联，将这些物件抽象成C/C++的类型系统。在设计ADT的时候，我们常常声明某个指针的别名，例如：

typedef struct node * list;

ADT的角度看，这个声明是再自然不过的事情，可以用list来定义一个列表。但从C/C++语法的角度来看，它其实是不符合C/C++声明语法的逻辑的，它暴力地将指针声明符从指针声明器中分离出来，这会造成一些异于人们阅读习惯的现象，考虑下面代码：

const struct node *p1;

typedef struct node *list;

const list p2;

p1类型是const struct node*，那么p2呢？如果你以为就是把list简单“代入”p2，然后得出p2类型也是const struct node*的结果，就大错特错了。p2的类型其实是struct node * const p2，那个const限定的是p2，不是node。造成这一奇异现象的原因是指针声明器被分割，标准中规定：

6.7.5.1 Pointer declarators

Semantics

If in the declaration ‘‘TD1’’, D1 has the form

* type-qualifier-listopt D

and the type specified for identin the declaration ‘‘T D’’ is

‘‘derived-declarator-type-listT’’

then the type specified for identis

‘‘derived-declarator-type-listtype-qualifier-list pointer to T’’

For each type qualifier in thelist, ident is a so-qualified pointer.

int *p, q, *k;

pk都是指针，但q不是，这是因为*p*k是一个整体指针声明器，以表示声明的是一个指针。编译器会把指针声明符左边的类型包括其限定词作为指针指向的实体的类型，右边的限定词限定被声明的标识符。但现在typedef struct node *list硬生生把*从整个指针声明器中分离出来，编译器找不到*，会认为const list p2中的const是限定p2的，正因如此，p2的类型是node * const而不是const node*

6.2.5 Types

incomplete types (types that describe objects but lack information needed to determinetheir sizes).

CC++关于不完整类型的语义是一样的。

class base;

struct test;

basetest只给出了声明，没有给出定义。不完整类型必须通过某种方式补充完整，才能使用它们进行实例化，否则只能用于定义指针或引用，因为此时实例化的是指针或引用本身，不是basetest对象。

extern int a[];

extern不能去掉，因为数组的长度未知，不能作为定义出现。不完整类型的数组可以通过几种方式补充完整才能使用，大括号形式的初始化就是其中一种方式：

int a[] = { 10, 20 };

struct test

{

int a;

double b;

char *p;

};

p指向字符串。这种方法造成字符串与结构体是分离的，不利于操作，如果把字符串跟结构体直接连在一起，不是更好吗？于是，可以把代码修改为这样：

char a[] = “hello world”;

struct test *PntTest = (struct test* )malloc( sizeof( struct test ) + strlen( a ) + 1 );

strcpy( PntTest + 1, a );

struct test

{

int a;

double b;

char c[0];

};

c就叫柔性数组成员，如果把PntTest指向的动态分配内存看作一个整体，c就是一个长度可以动态变化的结构体成员，柔性一词来源于此。c的长度为0，因此它不占用test的空间，同时PntTest->c就是“hello world”的首地址，不需要再使用(char* )( PntTest + 1 )这么丑陋的语法了。

6.7.2.1 Structure and unionspecifiers

As a special case, the lastelement of a structure with more than one named member may have an incompletearray type; this is called a flexible array member.

C99使用不完整类型实现柔性数组成员，标准形式是这样的：

struct test

{

int a;

double b;

char c[];

};

c同样不占用test的空间，只作为一个符号地址存在，而且必须是结构体的最后一个成员。柔性数组成员不仅可以用于字符数组，还可以是元素为其它类型的数组，例如：

struct test

{

int a;

double b;

float c[];

};

C90C++的数组对象定义是静态联编的，在编译期就必须给定对象的完整信息。但在程序设计过程中，我们常常遇到需要根据上下文环境来定义数组的情况，在运行期才能确知数组的长度。对于这种情况，C90C++没有什么很好的办法去解决（STL的方法除外），只能在堆中创建一个内存映像与需求数组一样的替代品，这种替代品不具有数组类型，这是一个遗憾。C99的可变长数组为这个问题提供了一个部分解决方案。

int n = 10, m = 20;

char a[n];

int b[m][n];

a的类型为char[n]，等效指针类型是char*b的类型为int[m][n]，等效指针类型是int(*)[n]int(*)[n]是一个指向VLA的指针，是由int[n]派生而来的指针类型。

1。代表该对象的标识符属于普通标识符（ordinary identifier）；

2。具有代码块作用域或函数原型作用域；

3。无链接性。

Ordinary identifier指的是除下列三种情况之外的标识符：

1。标签（label）；

2。结构、联合和枚举标记（struct taguion tagenum tag）；

3。结构、联合成员标识符。

VLA不能具有静态存储周期，但指向VLA的指针可以。

#include <stdio.h>

int n = 10;

inta[n];        /*非法，VM类型不能具有文件作用域*/

int(*p)[n];      /*非法，VM类型不能具有文件作用域*/

struct test

{

int k;

int a[n];     /*非法，a不是普通标识符*/

int (*p)[n];   /*非法，p不是普通标识符*/

};

int main( void )

{

int m = 20;

struct test1

{

int k;

int a[n];         /*非法，a不是普通标识符*/

int (*p)[n];       /*非法，a不是普通标识符*/

};

extern int a[n];       /*非法，VLA不能具有链接性*/

static int b[n];        /*非法，VLA不能具有静态存储周期*/

intc[n];            /*合法，自动VLA*/

int d[m][n];          /*合法，自动VLA*/

static int (*p1)[n] = d;  /*合法，静态VM指针*/

n = 20;

static int (*p2)[n] = d;  /*未定义行为*/

return 0;

}

#include <stdio.h>

int main( void )

{

int n = 10, m = 20;

char a[m][n];

char (*p)[n] = a;

printf( “%u %u”, sizeof( a ), sizeof( *p ) );

n = 20;

m = 30;

printf( “\n” );

printf( “%u %u”, sizeof( a ), sizeof( *p ) );

return 0;

}

VM除了可以作为自动对象外，还可以作为函数的形参。作为形参的VLA，与非VLA数组一样，会调整为与之等效的指针，例如：

void func( int a[m][n] ); 等效于void func( int (*a)[n] );

void func( int a[m][n] );

void func( int a[*][n] );

void func( int a[ ][n] );

void func( int a[*][*] );

void func( int a[ ][*] );

void func( int (*a)[*] );

*标记只能用在函数原型声明中。再举个例：

#include<stdio.h>

void func( int, int, inta[*][*] );

int main(void)

{

int m = 10, n = 20;

int a[m][n];

int b[m][m*n];

func( m, n, a );     /*未定义行为*/

func( m, n, b );

return 0;

}

void func( int m, int n, inta[m][m*n] )

{

printf( "%u\n", sizeof( *a ) );

}

void func( int, int, inta[const][*] );

void func( int, int, int (*const a )[*] );

void func( int, int, inta[const][*] );

……..

void func( int m, int n, inta[const m][n] )

{

int b[m][n];

a = b;        /*错误，不能通过a修改其代表的对象*/

}

static表示传入的实参的值至少要跟其所修饰的长度表达式的值一样大。例如：

void func( int, int, inta[const static 20][*] );

……

int m = 20, n = 10;

int a[m][n];

int b[n][m];

func( m, n, a );

func( m, n, b);     /*错误，b的第一维长度小于static 20*/

类型限定词和static关键字只能用于具有数组类型的函数形参的第一维中。这里的用词是数组类型，意味着它们不仅能用于VLA，也能用于一般数组形参。

总的来说，VLA虽然定义时长度可变，但还不是动态数组，在运行期内不能再改变，受制于其它因素，它只是提供了一个部分解决方案。

int *p = ( int* )malloc( n *sizeof( int ) );

int **p = ( int** )malloc( m *sizeof( int* ) );

for( i = 0; i < m; ++i )

p[i] = ( int* )malloc( n * sizeof( int ) );

for( i = 0; i < m; ++i )

free( p[i] );

free( p );

int *p = ( int* )malloc( m * n* sizeof( int ) );

int **q = ( int** )malloc( m *sizeof( int* ) );

for( i = 0; i < m; ++i )

q[i] = p + i * n;

int *p = ( int* )realloc( p, k* j * sizeof( int ) );

int **q = ( int** )realloc( q,k * sizeof( int* ) );

for( i = 0; i < k; ++i )

q[i] = p + i * j;

double a[100];

double **p = ( double**)malloc( 5 * sizeof( double* ) );

for( i = 0; i < 5; ++i )

p[i] = a + i * 20;

11-14 977

### “相关推荐”对你有帮助么？

• 非常没帮助
• 没帮助
• 一般
• 有帮助
• 非常有帮助

©️2022 CSDN 皮肤主题：大白 设计师：CSDN官方博客

1.余额是钱包充值的虚拟货币，按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载，可以购买VIP、C币套餐、付费专栏及课程。