C复习-数组与指针的区别+指针数组

参考: 里科《C和指针》


一维数组

int b[10];

在C中,在几乎所有使用数组名的表达式中,b是一个指针常量,即数组第一个元素的地址,其类型是数组元素的类型,所以b是一个指向int的常量指针。

但是数组和指针是完全不同的:数组具有确定数量的元素,而指针只是一个标量值。只有当数组名在表达式中使用时,编译器才会为它产生一个指针常量。只有两种情况下,数组名不用指针常量表示:当数组名作为sizeof或单目&的操作数时。前者返回整个数组的长度,后者返回的是指向数组的指针。

int a[10];
int *c;
c = &a[0];
// 等价于
c = a;
// 因为&a[0]是指向数组第一个元素的指针,也就是数组名的值

索引时用到逗号会如何?不报错,但是只取最后一个值

int b[] = {1,2,3};
// 输出3,因为逗号会只保留最后一个值,即b[2]
cout << b[1, 2] << endl;

array[sub]与 *( array + (sub) )的含义是相同的

int array[10];
int *ap = array + 2;
// 此时ap是指向array[2]的指针

ap[0] // 等价于*(ap+(0)),所以答案是array[2],是值
*ap + 6 // 等价于array[2] + 6
*(ap + 6) // 等价于array[8]
ap[-1] // 等价于array[2-1] = array[1]
2[array] // = *(2 + array) = array[2].编译器可以理解,但是人不行,不要写

假设指针和下标都正确使用,那么下标绝对不会比指针更有效率,而指针有时候比下标更有效率。

int array[10], a;
for ( a = 0; a < 10; a += 1 )
	array[a] = 0;

int array[10], *ap;
for ( ap = array; ap < array + 10; ap++ )
	*ap = 0;

上面的程序涉及指针的移动。对于下标法来说,因为array是指向array[0]的指针,每次取下标的时候都要将a与sizeof(int)相乘;但是对于指针法,在for语句上,每次需要移动sizeof(int),但这个值是在编译时计算出来的,循环时不用每次都计算,所以会更有效率一点。

a = get_value();
// 下面这两种的效率就一样,因为a是不确定的,都需要经过乘法计算
array[a] = 0;
*( array + a ) = 0;

不要为了效率上的细微差别而牺牲可读性。如果需要在自己的环境里取得最高效率,可能要看机器版本(看机器指令是否紧凑)

  1. 当以固定数目的增量在数组中移动时,使用指针变量比下标效率高,尤其上面那种for循环
  2. 声明为寄存器变量的指针通常比位于静态内存和堆栈中的指针效率高(具体看机器
  3. 如果可以通过通过内容判断循环结束,则不需要单独的计数器
  4. 那些必须在运行时求值的表达式,比&array[size]或array+size这样的常量表达式代价高
#define SIZE 50
int x[SIZE];
int y[SIZE];
int i;
int *p1, *p2;

// 初始版本
void
try1()
{
	for(i = 0; i < SIZE; i++)
		x[i] = y[i];
}

//指针版本
void
try2()
{
	for(p1 = x, p2 = y; p1 - x < SIZE;)
		*p1++ = *p2++
}

// 最终,最快
void
try5()
{
	register int *p1, *p2;
	for(p1 = x, p2 = y; p1 < &x[SIZE]; )
		*p1++ = *p2++;
}

这些改动在某些追求峰值效率的时刻比较重要,比如某些时候需要对即时发生的事件做出最快的反应。另外,对于那些占了绝大部分运行事件的代码,也需要着重改进。

一维数组作为函数参数

如果将数组名作为参数传递给一个函数,那么实际传递的是一个指针的拷贝,这个指针是指向数组第一个元素的指针。就是说,可以改变其指向位置的值,但是指向本身不能改。

只要有可能,函数的指针形参都应该声明为const

/*
** 把第2个字符串复制到第1个中
** string使用const是因为:a.方便阅读原型即知哪些参数不可修改 b.可以捕获试图修改的错误
** c.指允许向函数传递const参数
*/
void
strcpy1( char *buffer, char const *string) 
{
    while ((*buffer++ = *string++ ) != '\0' );
}

int main()
{
    char c[10] = {'a', 'b'};
    char d[10];
    char* s = c;
    char* b = d;
    strcpy1(b, s);
    for (int i = 0; i < 2; i++)
        cout << b[i] << endl;
    return 0;
}

虽然形参也可以写成 char buffer[],编译器也能接受,但是写成指针形式是更准确的。这也是为什么写函数原型时,一维数组形参不用写明元素数目,因为形参实际是指针,指向的是已经在其他地方分配好内存的空间。

字符串数组初始化

前者是初始化一个字符数组的元素,跟用列表初始化一致;后者是初始化一个指针,指向的是存储一个字符串常量“hello”的位置。

char msg[] = "hello";
char *msg2 = "hello";

多维数组

多维数组可以理解成切块,比如int a[3][4][5] 可以理解成先分3块,每个里面4小块,小块里面再分5块,然后每个最小的块是一个元素。用列表初始化的时候也这么写,就大括号嵌套。

int matrix[3][10];

matrix + 1指向的是第二行;*(matrix + 1)是指向第二行第一个元素的指针

*(matrix + 1) + 5 也是指针,只是指向的位置是matrix[1][5]

*( *(matrix + 1) + 5 ) 如果作为右值使用,就是取得存储在matrix[1][5]的值,如果作为左值使用,这个位置将被存一个新值

指向数组的指针

int matrix[3][10];
int (*p)[10] = matrix; // 它的移动是逐行的,p指向matrix第0行,移动到下一行的时候+10
// int *mp = matrix 是错误的,因为matrix是指向整型数组的指针,而mp是指向整型的指针
// int (*p)[] = matrix; 如果打算在指针上执行运算,就不能这样声明。
// 因为此时数组长度未知,那么移动到下一行要去哪里?编译器可能移动0,也可能报错

// 如果需要一个指针逐个访问整型元素,使用下面两种方法
int *pi = &matrix[0][0]; 
int *pi = matrix[0]; 

多维数组作为函数参数

void func2( int (*mat)[10] );
void func2( int mat[][10] );

void fun2( int **mat );

前两种是对的,因为编译器必须知道第2个及以后各维的长度才能对下标进行求值。第1维的长度不需要,除非要判断是否越界才行,但是计算下标值用不到。最后一个不对,因为mat指一个指向整型指针的指针,并不是指向整型数组的指针。

多维数组的初始化

int mat[2][3] = { 100, 101, 102, 110, 111, 112 };
int mat[2][3] = {
	{ 100, 101, 102 };
	{ 110, 111, 112 }
};

使用花括号初始化的好处是可以省略值,比如下面相当于给four_dim[0][0][0][0]赋值100,给four_dim[1][0][0][0]赋值200,其他是0。如果是使用列表初始化,那么100和200之间要填充0,一来繁琐二来易错。

int four_dim[2][2][3][5] = {
	{
		{
			{100}
		}
	},
	{
		{
			{200}
		}
	}
}

另外,使用花括号初始化时可以不写第1维,因为编译器可以推断出来。但是剩下的维度必须写明,因为如果需要编译器推测的话,必须要有一个完整写出的子初始值。那还是写明维度比较好,这样所有的初始值列表都可以不完整。

指针数组

先假定这是一个表达式。下标引用的优先级高于间接访问,所以api是某种类型的数组(长度是10),在取得一个数组元素后,执行间接访问,那么它的结果是一个整型值。所以api是一个数组,它的元素类型是指向整型的指针。

int *api[10];

下面第一种是创建了一个指针数组,每个指针元素都初始化指向不同的字符串常量,指针数组本身要占据空间,但是每个字符串常量占据的空间比矩阵小;第二种是创建了一个矩阵,每行是一个数组,存单词(含NUL),不需要指针。如果需要存储的字符串长度差不多,那么用矩阵ok,但如果差别很大,那用指针数组好一些,取决于指针占用的空间是否小于固定长度存储浪费的空间。

char const keyword[] = {
	"do",
	"for",
};

char const keyword[][9] = {
	"do",
	"for",
};

但除非是非常大的表,二者的区别很小,因此不重要。常用的方法如下,末尾加一个NULL指针可以在搜索时检测表的结尾,而无需预先知道表的长度。

char const *keyword[] = {
	"do",
	"for",
	NULL
};
// 使用NULL的好处:
for( kwp = keyword; *kwp != NULL; kwp++ )
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值