指针和数组分析

1 数组的访问方式

1 数组的访问方式

我们首先来看一个问题,数组名可以当作常量指针使用,那么指针是否也可以当作数组名来使用呢?

数组可以以下标的形式和以指针的形式两种方式进行访问:
在这里插入图片描述
在这里插入图片描述
下标形式VS指针形式:

  • 指针以固定增量在数组中移动时,效率高于下标形式。
  • 指针增量为1且硬件具有硬件增量模型时,效率更高。
  • 下标形式与指针形式的转换如下:
    在这里插入图片描述
    注意: 现代编译器的生成代码优化率已经大大提高,在固定增量时,下标形式的效率已经和指针形式相当;但从可毒性和代码维护的角度来看,下标形式更优。

示例代码:

#include <stdio.h>

int main()
{
    int a[5] = {0};
    int* p = a;
    int i = 0;
    
    for(i=0; i<5; i++)
    {
        p[i] = i + 1;
    }
    
    for(i=0; i<5; i++)
    {
        printf("a[%d] = %d\n", i, *(a + i));
    }
    
    printf("\n");
    
    for(i=0; i<5; i++)
    {
        i[a] = i + 10;
    }
    
    for(i=0; i<5; i++)
    {
        printf("p[%d] = %d\n", i, p[i]);
    }
    
    return 0;
}


2 指针和数组名的不同

2.1 指针和数组名的不同

虽然我们可以通过指针访问数组,但是我们必须知道数组名和指针仅使用方式相同:

  • 数组名的本质不是指针。
  • 指针的本质不是数组。

看如下代码:

// main.c
#include <stdio.h>

int main()
{
    extern int* a;

    printf("&a = %p\n", &a);
    printf("a = %p\n", a);
    printf("*a = %d\n", *a);

    return 0;
}

// extern.c
a[] = {1, 2, 3, 4, 5};

/* 分析:当编译器编译到extern int* a;这句话话的时候就会把a这个标识符当作一个指针看待。指针a的地址为就
是数组a标识符所在的地址,&a就是a所在的地址值,a就是里面保存的值(0x1),*a就是保存的地址值所对应的内存中
的值,因此会产生段错误。*/


3 数组作为函数参数

3.1 数组参数退化的意义

首先我们要知道C语言中只会以值拷贝的方式进行参数传递。
当我们向函数传递数组时,其实有如下两种实现方案:

  1. 将整个数组拷贝一份传入函数。
  2. 将数组名看作常量指针传数组首元素地址。

C语言选择的是第二种方式,是因为C语言以高效作为最初设计目标:

  • 参数传递的时候如果拷贝整个数组执行效率将大大下降。
  • 参数位于栈上,太大的数组拷贝将导致栈溢出。

3.2 一维数组作为函数参数

当一位数组作为函数参数时,编译器将其编译成对应的指针。
在这里插入图片描述
结论: 一般情况下,当定义的函数中有数组参数时,需要定义另一个参数来标示数组的大小。

3.3 二维数组作为函数参数

二维数组同样存在退化的问题:

  • 二维数组可以看作是一维数组。
  • 二维数组中的每个元素是一维数组。

二维数组参数中第一维的参数可以省略:

  • void f(int a[5]) <--> void f(int a[]) <--> void f(int* a)
  • void f(int a[3][3]) <--> void f(int a[][3]) <--> void f(int(*a)[3])

二维数组并不是退化为二级指针,而是退化为数组指针!指针数组才是退化为二级指针!

记住如下表格!!!
在这里插入图片描述
关于数组作为参数,还需要注意以下几点:

  • C语言无法向一个函数传递任意的多维数组,对于多维数组的函数参数只有第一维是可变的。
  • 必须提供除第一维之外的所有维长度
    • 第一维之外的维度信息用于完成指针运算。
    • N维数组的本质是一维数组,元素是N-1维的数组。
    • 对于多维数组的函数参数只有第一维是可变的。

传递与访问二维数组:

#include <stdio.h>

void access(int a[][3], int row)
{
    int col = sizeof(*a) / sizeof(int);
    int i = 0;
    int j = 0;
    
    printf("sizeof(a) = %d\n", sizeof(a));	// 4
    printf("sizeof(*a) = %d\n", sizeof(*a));	// 12
    
    for(i=0; i<row; i++)
    {
        for(j=0; j<col; j++)
        {
            printf("%d\n", a[i][j]);
        }
    }
    
    printf("\n");
}

void access_ex(int b[][2][3], int n)
{
    int i = 0;
    int j = 0;
    int k = 0;
    
    printf("sizeof(b) = %d\n", sizeof(b));	// 4
    printf("sizeof(*b) = %d\n", sizeof(*b));	// 24
    
    for(i=0; i<n; i++)
    {
        for(j=0; j<2; j++)
        {
            for(k=0; k<3; k++)
            {
                printf("%d\n", b[i][j][k]);
            }
        }
    }
    
    printf("\n");
}

int main()
{
    int a[3][3] = {{0, 1, 2}, {3, 4, 5}, {6, 7, 8}};
    int aa[2][2] = {0};
    int b[1][2][3] = {0};
    
    access(a, 3);
    access(aa, 2);	// compile warning
    access_ex(b, 1);
    access_ex(aa, 2);	// compile warning
    
    return 0;
}



4 数组指针和指针数组

4.1 数组指针

先来看下数组类型:

  • C语言中的数组有自己特定的类型。
  • 数组的类型由元素类型和数组大小共同决定。比如,int array[5]的类型为int[5]。

定义数组类型:

  • C语言中通过typedef为数组类型重命名,typedef type(name)[size]
  • 数组类型:
    • typedef int(AINT5)[5];
    • typedef float(AFLOAT10)[10];
  • 数组定义:
    • AINT5 iArray;
    • AFLOAT10 fArray;

数组指针:

  • 数组指针用于指向一个数组。
  • 数组名是数组首元素的起始地址,但并不是数组的起始地址。
  • 通过将取地址符&作用于数组名可以得到数组的起始地址。
  • 可通过数组类型定义数组指针:ArrayType* pointer;
  • 也可以直接定义:type(*pointer)[n];
    • pointer为数组指针变量名。
    • type为指向的数组的元素类型。
    • n为指向的数组的大小。

4.2 指针数组

指针数组:

  • 指针数组是一个普通的数组。
  • 指针数组中每个元素为一个指针。
  • 指针数组的定义:type* pArray[n];
    在这里插入图片描述
    指针数组的应用:
#include <stdio.h>
#include <string.h>

#define DIM(a) (sizeof(a)/sizeof(*a))

int lookup_keyword(const char* key, const char* table[], const int size)
{
    int ret = -1;
    
    int i = 0;
    
    for(i=0; i<size; i++)
    {
        if( strcmp(key, table[i]) == 0 )
        {
            ret = i;
            break;
        }
    }
    
    return ret;
}

int main()
{
    const char* keyword[] = {
            "do",
            "for",
            "if",
            "register",
            "return",
            "switch",
            "while",
            "case",
            "static"
    };
    
    printf("%d\n", lookup_keyword("return", keyword, DIM(keyword)));
    printf("%d\n", lookup_keyword("main", keyword, DIM(keyword)));

    return 0;
}



5 指针和数组的纠缠

有时候还是会分不清指针和数组名之间的关系,主要是二维数组,一维数组倒是没什么问题。懒得写太多,就直接拿代码测试下,如下:

// CodeTest.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <iostream>
#include <windows.h>

using namespace std;


int main()
{
	
	/****************************一维数组************************************/
	int array1[5] = { 1, 2, 3, 4, 5 };

	// 当数组名放在sizeof中代表的是整个数组
	cout << sizeof(array1) << endl;	//20
	

	// 当数组名直接做右值时,其代表数组空间中第一个元素的地址
	int* p1 = array1;
	//int* p11 = &array1;			//注意:&array1的类型是int (*)[5],数组指针
	cout << p1 << endl;				//0x003DF998
	cout << &array1 << endl;		//0x003DF998
	cout << p1[1] << endl;			//其本质也是简单的指针运算 + 解引用
	cout << array1[1] << endl;		//其本质也是简单的指针运算 + 解引用
	cout << *(p1 + 1) << endl;		//简单的指针运算
	cout << "------------------" << endl;
	
									
	/****************************二维数组************************************/
	int array2[3][3] = {
		{ 1, 2, 3 },
		{ 4, 5, 6 },
		{ 7, 8, 9 }
	};

	// 当数组名放在sizeof中代表的是整个数组
	cout << sizeof(array2) << endl;	//36

	// 当数组名直接做右值时,其代表数组空间中第一个元素的地址
	int (*p2)[3] = array2;		//二维数组也可以看成是一维的,不过每一维都是一个一维数组
								//所以其数组空间的第一个元素的地址就是指向一维数组的
	//int (*p22)[3] = &array2;	//&array2的类型是int (*)[3][3]
	cout << (*p2)[0] << endl;	//*p2得到其所指向的一维数组的第一个元素的指针
	cout << *(*p2 + 0) << endl;

	cout << (*array2)[0] << endl;	//[]有两个作用:1.指针运算 2.解引用
									//*array2 得到二维数组中第一个元素(也就是一个一维数组)中
									//的第一个int类型元素的地址,然后通过[]运算符进行地址偏移
									//和解引用然后得到第一个int类型元素的值
	cout << *array2[0] << endl;		//array2[0]得到第一个元素(也就是一个一维数组)中第一个int类型
									//的地址值,然后再通过*拿到其中存储的值

	//其实array2、&array2、*array2的地址值都是一样的,只是其类型不同而已,需要注意。

	system("pause");
    return 0;
}



参考资料:

  1. C语言进阶剖析教程
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值