数组
数组的定义和形式
数组由数据类型相同的一系列元素组成。数组可以用来储存一系列类型相同的元素。
数组元素在内存中是连续储存的。
需要使用数组时,通过声明告诉编译器数组内含有多少个元素和这些元素的类型。编译器根据这些数据正确地创建数组。
普通变量可以使用的类型,数组元素都可以使用。
数组元素就可以看作一个普通的变量。
使用数据元素和使用同类型的变量的方式一样。
可以直接给数组元素赋值, 代码示例:
#include<stdio.h> int main(void) { int str1[10]; str1[0] = 9; str1[9] = 10; return 0; }
可以把值直接读入指定的元素中, 代码示例:
#include<stdio.h> int main(void) { int str1[10]; scanf("%d", &str1[0]); printf("%d\n", str1[0]); return 0; }
结果:
1 1
定义数组的代码示例:
int main(void) { float candy[365]; char code[12]; int states[50]; }
整个数组有一个名字,candy、code、states 都是数组的名字。
方括号 []
表明 candy、code、states 都是数组,方括号中的数字表明数组中的元素个数。
通过使用数组下标(也叫索引)访问数组中的各个元素。
The numbers used to identify the array elements are called subscripts , indices , or offsets.
索引必须是整型常量,如 #define 定义的符号常量,不能是 const 定义的变量。
数组元素的索引从 0 开始,如 candy[0] 表示数组 candy 的第一个元素。
编译器不检查索引是否越界。
声明了数组时就分配了空间,如:
#include<stdio.h> int main(void) { char a[10]; printf("%zd\n" , sizeof(a)); return 0; }
结果:
10
当 sizeof 运算符作用于数组名时, 得到整个数组占用的字节数, 此时数组名不会被当成指针.
初始化数组
可以用初始化列表初始化数组。
初始化列表:用花括号括起来,用逗号分隔值。从 ANSI C 开始支持初始化列表。
示例:
#include<stdio.h> int main(void) { int a[3] = {1 , 2 , 3}; // 这是完整初始化,给每一个数组元素都用初始化列表初始化了一个值 for (int index = 0 ; index < 3 ; index++) printf("%d " , a[index]); printf("\n"); return 0; }
结果:
1 2 3
初始化之后数组还可以改变:
#include<stdio.h> int main(void) { int a[3] = {1 , 2 , 3}; for (int index = 0 ; index < 3 ; index++) printf("%d " , a[index]); printf("\n"); a[0] = 10; for (int index = 0 ; index < 3 ; index++) printf("%d " , a[index]); printf("\n"); return 0; }
结果:
1 2 3 10 2 3
只能在声明数组时用初始化列表:
#include<stdio.h> int main(void) { int a[3]; a[3] = {1 , 2 , 3}; for (int index = 0 ; index < 3 ; index++) printf("%d " , a[index]); printf("\n"); return 0; }
结果:
5:9: error: expected expression before '{' token a[3] = {1 , 2 , 3};
const 数组
用 const 声明的数组是只读数组,不能修改值,所以只能用初始化列表进行初始化。
示例:
#include<stdio.h> int main(void) { const int a[3] = {1 , 2 , 3}; a[1] = 2; return 0; }
结果:
5:7: error: assignment of read-only location 'a[1]' a[1] = 2;
没有初始化的数组
声明数组而没有初始化,则数组元素是当前内存中已有的值。
示例:
#include<stdio.h> int main(void) { int a[3]; for (int index = 0 ; index < 3 ; index++) printf("%d\n" , a[index]); return 0; }
结果:
4200800 0 2334720
部分初始化的数组
示例:
#include<stdio.h> #define SIZE 4 int main(void) { int a[SIZE] = {9 , 8}; for (int index = 0 ; index < SIZE ; index++) printf("%d\n" , a[index]); return 0; }
结果:
9 8 0 0
后面没有初始化的默认设为 0,但这只适用于自动存储类别的数组。
如果初始化列表元素多余数组元素,编译器未报错,只是将前几个匹配的数初始化上了。
#include<stdio.h> #define SIZE 4 int main(void) { int a[SIZE] = {9 , 8 , 7 , 6 , 100 , 1000}; for (int index = 0 ; index < SIZE ; index++) printf("%d\n" , a[index]); return 0; }
结果:
9 8 7 6
如果省略数组声明中的方括号内的数字,编译器自动匹配数组元素个数和初始化列表中元素个数相等。
示例:
#include<stdio.h> #define SIZE 4 int main(void) { int a[] = {9 , 8 , 7 , 6 , 100 , 1000}; for (int index = 0 ; index < sizeof(a) / sizeof(a[0]) ; index++) printf("%d\n" , a[index]); // 整个数组的大小除以单个元素的大小就是数组元素的个数 return 0; }
结果:
9 8 7 6 100 1000
初始化字符数组
初始化字符数组有一个特殊方法:
#include<stdio.h> int main(void) { char a[] = "China"; for (int index = 0 ; index < sizeof(a) / sizeof(a[0]) ; index++) printf("%c\n" , a[index]); return 0; }
结果:
C h i n a
注意最后有一个空行,因为这个数组被自动匹配成6个字符,即 sizeof(a) = 6
,最后一个是空字符,打印出来什么也没有,是一个空行。
下面程序可以去掉这个空行:
#include<stdio.h> int main(void) { char a[] = "China"; for (int index = 0 ; index < sizeof(a) / sizeof(a[0]) - 1; index++) printf("%c\n" , a[index]); return 0; }
结果:
C h i n a
这时没有打印最后一个字符,即空字符。
自动匹配字符数组长度时最后会自动加上空字符,这时字符数组也是字符串。如:
#include<stdio.h> int main(void) { char a[] = "China"; for (int index = 0 ; index < sizeof(a) / sizeof(a[0]) ; index++) printf("%c\n" , a[index]); printf("%s\n" , a); return 0; }
结果:
C h i n a China
这里a不光是字符数组,也是字符串。所以既可以用字符数组的方式输出,也可以当作字符串输出。
如果指定字符数组长度且长度刚好匹配初始化字符串的长度或数组长度小于初始化字符串的长度,字符数组最后一个字符就不是空字符,此时字符数组也就不再是字符串。
示例:
#include<stdio.h> int main(void) { char a[3] = "China"; for (int index = 0 ; index < 3 ; index++) printf("%c\n" , a[index]); printf("%s\n" , a); return 0; }
结果:
C h i Chi
这里 a 只是字符数组,不是字符串。当把a作为字符串输出时,最后一个字符出错。
指定字符数组的长度是初始化字符串的字符个数加一时,最后一个字符数组元素会自动是空字符。字符数组储存的元素是字符,而空字符的 ASCII 码值为 0. 如:
#include<stdio.h> int main(void) { char a[6] = "China"; for (int index = 0 ; index < 6 ; index++) printf("%c\n" , a[index]); printf("%s\n" , a); return 0; }
结果:
C h i n a China
指定字符数组的长度比初始化字符串的字符个数加一还要大时,后面的字符数组元素全部都会自动是空字符。如:
#include<stdio.h> int main(void) { char a[8] = "China"; for (int index = 0 ; index < 8 ; index++) printf("%c\n" , a[index]); printf("%s\n" , a); return 0; }
结果:
C h i n a China
指定初始化器(designated initializer)
C99 新增了指定初始化器,利用它可以初始化指定的数组元素。
使用方法:在初始化列表中用带方括号的下标指明待初始化的元素。未被初始化的元素默认为 0. 如:
#include<stdio.h> int main(void) { int a[5] = {[3] = 100}; for (int index = 0 ; index < 5 ; index++) printf("%d\n" , a[index]); return 0; }
结果:
0 0 0 100 0
示例2:
#include<stdio.h> int main(void) { int a[15] = {10 , 11 , [4] = 100 , 99 , 98 , [9] = 1 , 2 , 3 , [1] = 88}; for (int index = 0 ; index < 15 ; index++) printf("%d\n" , a[index]); return 0; }
结果:
10 88 0 0 100 99 98 0 0 1 2 3 0 0 0
程序中
int a[15] = {10 , 11 , [4] = 100 , 99 , 98 , [9] = 1 , 2 , 3 , [1] = 88};
初始化列表的 10
和 11
是初始化 a[0]
和 a[1]
, [4] = 100
是初始化 a[4]
,后面的 99 , 98
是初始化紧接着的 a[5]
和 a[6]
,中间跳过的 a[2]
和 a[3]
自动为 0. [9] = 1 , 2 , 3,
同理。 [1] = 88
又将 a[1]
初始化为 88,覆盖了初始化列表前面的初始化。
如果未指定数组元素个数,则编译器会自动初始化刚好能装得下所有元素。如:
int a[] = {[6] = 6}; // 数组a有7个元素,下标从0到6 int a[] = {1 , [6] = 6}; // 数组a有7个元素,下标从0到6 int a[] = {[6] = 6 , 7 , 8 , [12] = 12 , 20 , 100}; // 数组a有15个元素,下标从0到14
给数组元素赋值
不可以把数组作为一个整体赋值给另一个数组,除了初始化之外不可以用初始化列表即花括号列表的形式赋值。
数组下标越界
考虑到影响执行的速度, C 编译器不会检查数组的下标是否正确.
程序示例:
#include<stdio.h> int main(void) { int str1[10]; str1[0] = 9; str1[9] = 10; str1[10] = 11; // 出错,下标越界 return 0; }
分析: 编译器不会查找这样的错误, 当程序运行时, 这会导致数据被放置在已被其他数据占用的地方, 可能会破坏程序的结果, 甚至导致程序异常中断.
二维数组
int a[10][20]; // 定义一个二维数组
int a[2][3]= { {1 , 2 , 3}, {4 , 5 , 6} }; // 初始化一个二维数组,完全匹配
以上代码等价于:
int a[2][3]= { 1 , 2 , 3, 4 , 5 , 6 }; // 初始化一个二维数组,完全匹配
不完全匹配:
int a[2][3]= { 1 , 2 , 3 }; // 初始化一个二维数组,不完全匹配
此时按照初始化列表中的元素的顺序依次初始化,然后后面的元素为 0
int a[4][3]= { {1 , 2 }, {4 , 5 , 6} }; // 初始化一个二维数组,不完全匹配,缺失部分全部默认为0
int a[4][3]= { {1 , 2 }, {4 , 5 , 6 , 7 , 8 , 9} }; // 初始化一个二维数组,初始化列表元素多了编译器也未报错
数组和指针
数组名是数组首元素的地址。
示例:
#include<stdio.h> #define SIZE 4 void fun(int * u); int main(void) { int a[SIZE]; printf("%zd\n" , sizeof(a)); // 打印数组的大小 fun(a); return 0; } void fun(int * u) { printf("%zd\n" , sizeof(u)); // 打印指针本身的大小 }
结果:
16 4
说明本系统用四字节存储指针.
如果有:
int a[10];
那么有:
a == &a[0];
a 和 &a[0] 都是常量
声明了一个数组之后,它在内存中的位置就确定了.
可以把 a 或 &a[0] 赋给指针,但必须是指向这个数组元素的类型的指针,即让指针指向这个数组的首元素,然后改变指针的值,让指针指向这个数组中的其他元素.
a 本身也就是一个指针, 完整地说, a 是一个指向数组元素类型的指针, 指向数组 a 的首元素, 但指针 a 的值不能改变, 即不能指向其他任何位置, 只能一直指向数组a的首元素.
示例:
#include<stdio.h> #define SIZE 4 int main(void) { int a[SIZE]; double b[SIZE]; int index; int * pointer_a; double * pointer_b; printf("%22s %11s\n" , "int" , "double"); for (index = 0 ; index < SIZE ; index++) { printf("pointer + %d : %10p %10p\n" , index , pointer_a + index , pointer_b + index); } return 0; }
结果:
int double pointer + 0 : 00000000 003E4000 pointer + 1 : 00000004 003E4008 pointer + 2 : 00000008 003E4010 pointer + 3 : 0000000C 003E4018
地址按字节编码,这个系统中,int占4字节,double占8字节.一个占多个字节的对象的地址是所占字节中第一个字节的地址.
在C语言中,指针加1是以字节为单位增加一个存储单元,对数组而言就是把指针指向下一个元素,即这是指针的值是下一个数组元素的地址.
a[n]=*(a+n)
将数组名作为实参进行传递时,因为数组名是地址,所以可以在被调函数中修改原来数组的元素.且形参必须是指向数组元素类型的指针.
如函数声明:
int fun(int * a);
在函数声明和函数定义中,声明形参处,可以这样替换:
int fun(int a[]);
这里的int * a和int a[]都表示a是一个指向int的指针,而int a[]不仅表示指针a指向一个int类型的值,还表示这个值是一个int类型数组的元素.
这四种函数原型都是等价的:
int fun(int * a); int fun(int a[]); int fun(int *); int fun(int []);
这两种函数定义等价:
int fun(int * a) int fun(int a[])
如果a是指针,那么a++成立,如果a是数组名,那么a++不成立.数组名是常量,不能改变.
在把数组名作为实参传递时,被调函数可以改变数组元素,也可以保护数组元素不被改变。
被调函数的指针形参用const修饰,可以防止被调函数改变数组元素。示例:
#include<stdio.h> void change(const int * p); int main(void) { int a[4]={1,2,3,4}; change(a); for (int i = 0 ; i < 4 ; i++) printf("%d " , a[i]); printf("\n"); return 0; } void change(const int * p) { for (int i = 0 ; i < 4 ; i++) { p[i]++; } }
结果:
19:7: error: increment of read-only location '*(p + (sizetype)((unsigned int)i * 4u))' p[i]++; ^~
如果只是在主调函数中定义数组时用const修饰,将不能防止被调函数修改数组元素。如下:
#include<stdio.h> void change(int * p); int main(void) { const int a[4]={1,2,3,4}; change(a); for (int i = 0 ; i < 4 ; i++) printf("%d " , a[i]); printf("\n"); return 0; } void change(int * p) // 传函时是将const数据传递给非指向const的指针,导致这个指针可以修改const数据 { for (int i = 0 ; i < 4 ; i++) { p[i]++; } }
结果:
2 3 4 5
可以将非const数据的地址赋值给非指向const的指针,用这个指针改变非const数据不会出错。
C标准规定,用非const标识符改变const数据导致的结果是未定义的。
于是,不能直接修改const数据,通过别的非const标识符改变const数据结果不确定。
修饰符const可以防止数组元素在定义这个数组的函数中再改变数组元素。如:
#include<stdio.h> int main(void) { const int a[4]={1,2,3,4}; a[1]=10; return 0; }
结果:
6:6: error: assignment of read-only location 'a[1]' a[1]=10; ^
对于指向const的指针,不能用这个指针改变它所指向的值,如果这个值不是const,那么这个值可以改变,只是不能通过这个指针来改变,但指向const的指针的值可以变,即指针可以指向别处,如:
#include<stdio.h> int main(void) { int a[4]={1,2,3,4}; int b[2]={1,2}; const int * p = a; *p=10; // 指针表示法,不可以 p[2]=10; // 数组表示法,不可以,p是指向数组第一个元素,但也不能用p来改变数组其余元素的值 a[2]=10; // 可以,因为a不是const p++; // 可以,让p指向a[1] p=b; // 改变p的指向 return 0; }
于是,可以将指向const的指针用于函数形参中,表示这个函数不会用这个指针形参改变主调函数的值。这样,指向const的指针可以接受const数组作为实参。
可以将const数据或非const数据的地址赋给指向const的指针。
可以声明并初始化一个只能指向一个位置、不能指向别的位置的指针,如:
int a[3]={1,2,3}; int * const p = a; // 必须同时初始化,此时p指向a[0] p = &a[1]; // 尝试让p指向a[1],错误,const指针不能指向别处,只能指向初始化时指向的位置 *p = 10; // 可以改变const指针的值,因为这里的a不是const,这里是将a[0]改变值为10
声明指针时可以使用两次const,该指针既不能指向除了初始化之外的其他位置,也不能用这个指针来改变指向的地址上的值。如:
int a[3]={1,2,3}; const int * const p = a; p = &a[2]; // 不可以将p指向别处 *p = 10; // 不可以用p修改p指向的位置的值
总结一下:
指向const的指针:
const int * p;
不能通过p来改变p指向的内存位置的值,如果这个位置的值不是const,那么可以通过别的方法改变这个值。
const指针:
int a[3]={1,2,3}; int * const p=a;
const指针必须在声明的同时初始化,const指针的值不能变,即只能指向初始化时指定的位置。
指向const的const指针:
const int * const p;
同时具备上述两种性质。
多维数组和指针
声明一个指针:
int a[2][3];
数组名a是数组a的首元素的地址,数组a有两个元素a[0], a[1],都是含有三个int值的数组,于是a的首元素是一个含三个int值的数组,于是a是这个含三个int值的数组的地址,即a是一个数组的地址。于是有:
a=&a[0];
a[0]是一个含三个int值的数组,则a[0]是一个数组名,即是一个指针,a[0]的值是数组a[0]的首元素的地址,于是有:
a[0]=&a[0][0];
即a[0]是指向一个int类型的指针,a[0]的值是一个int类型的对象的地址。a是指向一个内含三个int类型值的数组的指针,a的值是一个数组的地址。
数组a[0]
和整数a[0][0]
开始于同一个地址,所以有:
a=a[0];
a和a[0]都是数组名,即都是指针,都是const指针,不能指向别处。
a指向a[0],a+1指向数组a[1],这里一个数组占用3个int类型内存空间,所以这里指针a加1是移动了三个int类型的内存空间。
a[0]指向a[0][0]
,a[0]+1是指向a[0][1]
,这里指针加1移动了一个int类型的内存空间。
所以有:
但:
a指向a[0],a[0]指向a[0][0]
,于是a是指向指针的指针.
a=a[0]; // 两边都表示指针,两边指针仅是值在数字本身上是相等的 a[0]=&a[0][0]; *a=a[0]; // 可理解为左边是对指针解引用,右边是数组,即解引用得到的值是一个数组,或者理解为两边都是指针 // *a 和 a[0]等价,*(a+1)和a[1]等价,等等 *a=a; *a[0]=a[0][0]; // 左边是对一个指针解引用 *a=&a[0][0]; **a=*&a[0][0]=a[0][0]; // 对a解引用一次得到的是a指向的指针的值,即一个地址,再解引用这个地址,得到这个地址指向的内存位置的值,解引用是对指针或地址进行运算,找到地址对应的内存位置储存的值 // a是地址的地址,必须解引用两次才能获得内存位置储存的值, // 地址的地址或指针的指针叫做双重间接(double indirection)
示例:
#include<stdio.h> int main(void) { printf("One int has %zd bytes.\n\n" , sizeof(int)); int a[4][2]= { {1,2}, {3,4}, {5,6}, {7,8} }; printf("a = %p , a+1 = %p\n" , a , a+1); printf("a[0] = %p , a[0]+1 = %p\n" , a[0] , a[0]+1); printf("*a = %p , *a + 1 = %p\n" , *a , *a + 1); printf("a[0][0] = %d\n" , a[0][0]); printf("*a[0] = %d\n" , *a[0]); printf("**a = %d\n" , **a); printf("a[3][1] = %d\n" , a[3][1]); printf("*(*(a+2)+1) = %d\n" , *(*(a+2)+1)); return 0; }
结果:
One int has 4 bytes. a = 0061FF00 , a+1 = 0061FF08 a[0] = 0061FF00 , a[0]+1 = 0061FF04 *a = 0061FF00 , *a + 1 = 0061FF04 a[0][0] = 1 *a[0] = 1 **a = 1 a[3][1] = 8 *(*(a+2)+1) = 6
分析:
一个int占用4个字节.
a指向第一个数组,a的值是第一个数组的地址,a+1指向第二个数组,a+1的值是第二个数组的地址,一个数组有两个int值,一个数组占用空间是两个int的空间,即8字节.
一个数组的地址是数组第一个元素的地址.
a[0]指向第一个数组的第一个元素,a[0]的值是第一个数组的第一个元素的地址.所以a的值(第一个数组的地址)和a[0]的值(第一个数组的第一个元素的地址)相等,即a=a[0]
.
a指向数组,a+1移动一个数组的字节数,这里是8字节.
a[0]指向int类型值,a[0]+1移动一个int类型占用的字节个数,这里是4字节.
*a
是指针a指向的内存位置存储的值,指针a指向指针a[0],则*a
是a[0]的值,即*a=a[0]
*a
也是一个指针,和a[0]一样,且都指向同一种数据类型,这里是都指向int类型.*a
也指向第一个数组的第一个元素.
a+2是一个指针,指向第三个数组,*(a+2)
是一个指针,指向第三个数组的第一个元素,*(a+2)+1
是一个指针,指向第三个数组的第二个元素,*(*(a+2)+1)
是第三个数组的第二个元素的值.*(*(a+2)+1)
和a[2][1]
相等,即:
*(*(a+2)+1) = a[2][1];
使用两个解引用运算符或者使用两对方括号都能获得值,也可以使用一个解引用运算符和一对方括号也能获得值.
声明指向数组的指针
声明一个指向含有两个int类型值的数组的指针:
int (* p)[2];
p是一个指针,指向一个数组,这个数组含有两个元素,这两个元素是int类型.
声明一个含有两个指针元素的数组:
int * p[2];
[]优先级比*高
,这样就定义了p是一个有两个元素的数组,*表示
这两个元素是指针,int表示指针指向int类型.或者看作int *
一体的,int *
是指向int的指针类型.
示例:
#include<stdio.h> int main(void) { int a[4][2]= { {1,2}, {3,4}, {5,6}, {7,8} }; int (*p)[2]; p=a; printf("p = %p , p+1 = %p\n" , p , p+1); printf("p[0] = %p , p[0] + 1 = %p\n" , p[0] , p[0] + 1); printf("*p = %p , *p+1 = %p\n" , *p , *p+1); printf("p[0][0] = %d\n" , p[0][0]); printf("*p[0]=%d\n",*p[0]); printf("**p=%d\n",**p); printf("p[1][1]=%d\n",p[1][1]); printf("*(*(p+2)+1) = %d\n" , *(*(p+2)+1)); return 0; }
虽然p是一个指针,不是数组名,但也可以用p[1][1]
这样的写法。可以用数组表示法或指针表示法来表示一个数组元素,既可以使用数组名,也可以使用指针名。
可以将指向同类型的指针相互赋值:
int *a , *b; int c=1; a=&c; b=a; // 同类型指针赋值
如果指针a指向含三个元素的数组,指针b指向含4个元素的数组,则不能互相赋值。或者数组元素个数相等但类型不同也不能互相赋值。指针互相赋值要求很严格,必须完全一样。
声明一个指向指针指针:
int **p;
p的值即这个地址指向的内存位置的值也是一个地址,且这个地址指向的内存位置必须存放一个int值。
即p指向一个指向int的指针,*p是指向int的指针。
处理多维数组的函数
主要是定义形参。
假如有一个数组如下:
int a[3][4];
那么a的每一个元素都是含有4个int类型元素的数组,对应的处理这个数组的函数的形参应定义为:
void fun(int (*p)[4]);
或定义为:
void fun(int p[][4]);
只有定义形参时即函数头或函数声明中第二种写法正确。
第二种写法中的空的方括号表明p是一个指针,就像在第一种写法里用*表示
p是一个指针一样。
第二个方括号和里面的数字4表示指针p指向一个含有4个元素的数组,前面的int表示这个数组的元素是int类型。
在函数原型中,形参变量的名称可以省略。即:
void fun(int [][4]);
变长数组(variable-length array,VLA)
C99新增,C11改为可选项。
] + 1);
printf("p = %p , p+1 = %p\n" , p , p+1);
printf("p[0][0] = %d\n" , p[0][0]);
printf("p[0]=%d\n",p[0]);
printf("p=%d\n",p);
printf("p[1][1]=%d\n",p[1][1]);
printf("((p+2)+1) = %d\n" , ((p+2)+1));
return 0;
}
虽然p是一个指针,不是数组名,但也可以用`p[1][1]`这样的写法。可以用数组表示法或指针表示法来表示一个数组元素,既可以使用数组名,也可以使用指针名。 可以将指向同类型的指针相互赋值: ```c int *a , *b; int c=1; a=&c; b=a; // 同类型指针赋值
如果指针a指向含三个元素的数组,指针b指向含4个元素的数组,则不能互相赋值。或者数组元素个数相等但类型不同也不能互相赋值。指针互相赋值要求很严格,必须完全一样。
声明一个指向指针指针:
int **p;
p的值即这个地址指向的内存位置的值也是一个地址,且这个地址指向的内存位置必须存放一个int值。
即p指向一个指向int的指针,*p是指向int的指针。
处理多维数组的函数
主要是定义形参。
假如有一个数组如下:
int a[3][4];
那么a的每一个元素都是含有4个int类型元素的数组,对应的处理这个数组的函数的形参应定义为:
void fun(int (*p)[4]);
或定义为:
void fun(int p[][4]);
只有定义形参时即函数头或函数声明中第二种写法正确。
第二种写法中的空的方括号表明p是一个指针,就像在第一种写法里用*表示
p是一个指针一样。
第二个方括号和里面的数字4表示指针p指向一个含有4个元素的数组,前面的int表示这个数组的元素是int类型。
在函数原型中,形参变量的名称可以省略。即:
void fun(int [][4]);
变长数组(variable-length array,VLA)
C99新增,C11改为可选项。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术