第 10 章 数组和指针

一、数组

1.1 数组的定义

​ 通过学习本章,数组就是占用一段连续的内存地址的一个抽象,它存储同类型的元素。

数组

1.2 声明数组

类型 变量名[个数]

类型是为了让计算机知道每一个单元格是多大,数组的使用其实就是指针,访问指定位置的元素,就是指针移动到指定的位置,如声明一个 int arr[5] ,访问 arr[4] ,就是 访问*(arr + 4) ,这里的指针移动了 4 个单位,一个单位就是 int 所占的空间(代码见 2.1 (b) );数组变量名是标识符;个数×类型大小就是这个数组在内存中所占的空间大小。

1.3 初始化数组

​ 在使用数组之前必须进行初始化,否则会产生意想不到的东西;

​ 例如:

int a[5];	//声明并初始化
int i;

for(i = 0; i < 5; i++)
    printf("%d ", a[i]);
/*
输出结果为:
4196144 0 4195472 0 974532160 (可能根据系统的不同而不同)
*/

​ 使用 { } 的方式初始化,如果不指定元素,则自动初始化为0;

​ 例:

int b[5] = {};	//声明并初始化
int i;

for(i = 0; i < 5; i++)
	printf("%d", b[i]);

/*
输出结果为:
0 0 0 0 0
*/

​ 使用 { } 这种方式初始化如果数组声明的个数与初始化个数不匹配,则默认把其他元素初始化为0;

int c[5] = {5, 6};	//声明并初始化
int i;

for(i = 0; i < 5; i++)
	printf("%d", c[i]);

/*
输出结果为:
5 6 0 0 0
*/

​ 使用 { } 可以初始化指定元素的值(C99的新特性)。

​ 例:

int d[5] = {[3] = 3, [4] = 4};	//声明并初始化
int i;

for(i = 0; i < 5; i++)
	printf("%d", c[i]);

/*
输出结果为:
0 0 0 3 4
*/

注:这里为了方便直接使用数值常量 5 来作为数组大小,规范的写法应该使用 #define 定义的常量。

1.4 多维数组

​ 如 1.1 的图所示,多维数组其实和一维数组没有本质的区别,声明及初始化多维数组的方式如下:

int arr[5][5] = {{}};	//声明并初始化
int arr2[5][5] = {[2] = {1, 2, 3}};
int i;
int j;

for(i = 0; i < 5; i++)
{
    for(j = 0; j < 5; j++)
    {
        printf("%d ", arr[i][j]);
    }
    printf("\n");
}

for(j = 0; j < 5; j++)
{
	printf("%d ", arr[2][j]);
}

/*
输出结果:
0 0 0 0 0 
0 0 0 0 0 
0 0 0 0 0 
0 0 0 0 0 
0 0 0 0 0

1, 2, 3, 0, 0
*/

二、数组和指针

2.1 数组和指针的关系

​ 数组和指针的关系十分密切,数组表示法其实就是在变相地使用指针,可以使用指针标识数组的元素和获得元素的值。实际上,C语言标准在描述数组的时候借助了指针。

2.1.1 数组名就是数组元素的首地址,即 arr = &(arr[0])

​ 例:

#include <stdio.h>
#define SIZE 5

int main(void)
{
        int arr[SIZE] = {};		//声明并初始化

    	//判断是否相等
        (arr == &arr[0]) ? printf("equal! \n") : printf("not equal! \n");

        return 0;
}

/*
输出结果:
equal! 
*/
2.1.2 可以使用指针访问数组中的元素

​ Ⅰ. 访问一维数组中的元素

​ 例:

#include <stdio.h>
#define SIZE 5

int main(void)
{
        int arr[SIZE] = {[4] = 5};
        int * p = arr;

    	printf("使用数组访问 arr[4]: %d \n", arr[4]);
        printf("使用指针访问 arr[4]: %d \n", *(p + 4));
    	//注:*(p + 4) <=> p[4] <=> arr[4]
    	// 这样更能深刻理解数组的名字就是首地址
    
    	//使用指针改变数组的元素
		*(p + 4) = 4;
        printf("使用指针改变 arr[4]: %d \n", *(p + 4));
        return 0;
}

/*
输出结果:
使用数组访问 arr[4]: 5 
使用指针访问 arr[4]: 5 
使用指针改变 arr[4]: 4
*/

​ Ⅱ. 访问二维数组中的元素

/*
	像上述方法一样使用指针,
	该方法会产生警告,
	array4.c:7:12: warning: initialization from incompatible pointer type [enabled by 
	default]
  	int * p = arr;
*/
#include <stdio.h>
#define SIZE 5

int main(void)
{
        int arr[SIZE][SIZE] = {{}};
        int * p = arr;

        arr[2][0] = 10;

        printf("使用数组访问 arr[2][0]: %d \n", arr[2][0]);
        printf("使用指针访问 arr[2][0]: %d \n", *(p + 10));

        *(p + 10) = 4;
        printf("使用指针改变 arr[2][0]: %d \n", *(p + 10));

        return 0;
}
/*
输出结果:
使用数组访问 arr[2][0]: 10 
使用指针访问 arr[2][0]: 10 
使用指针改变 arr[2][0]: 4 
*/
/*
	把指针声明为指向数组的指针,该数组内含 SIZE 个 int 类型值。
	这是最规范的写法
*/
#include <stdio.h>
#define SIZE 5

int main(void)
{
        int arr[SIZE][SIZE] = {{}};
        int (* p) [SIZE] = arr;	// p 为指向数组的指针,该数组内含 SIZE 个 int 值
		//注:arr <=> &(arr[0])
    
        arr[2][0] = 10;

        printf("使用数组访问 arr[2][0]: %d \n", arr[2][0]);
        printf("使用指针访问 arr[2][0]: %d \n", *(*(p + 2)));
    	//注:*(*(p + 2)) <=> p[2][0] <=> arr[2][0]

        *(*(p + 2)) = 4;
        printf("使用指针改变 arr[2][0]: %d \n", *(*(p + 2)));

        return 0;
}
/*
输出结果:
使用数组访问 arr[2][0]: 10 
使用指针访问 arr[2][0]: 10 
使用指针改变 arr[2][0]: 4 
*/

2.2 指针表示数组

2.2.1 指针表示一维数组

数组表示:

数组类型 数组名字 [个数]

指针表示:

数组类型 * 指针名字

例:

#include <stdio.h>
#define SIZE 5
int main(void)
{
        int a[SIZE]= {[4] = 10};
        char b[SIZE] = {[4] = 'a'};
        float c[SIZE] = {};

        int * pa = a;
        char * pb = b;
        float * pc = c;

       //输出数组和指针的地址,发现它们的地址相同
        printf("arr_a: %p, pointer_a: %p \n", a, pa);
        printf("arr_b: %p, pointer_b: %p \n", b, pb);
        printf("arr_c: %p, pointer_c: %p \n", c, pc);
       
        return 0;
}
/*
arr_a: 0x7fffc29a6510, pointer_a: 0x7fffc29a6510 
arr_b: 0x7fffc29a6500, pointer_b: 0x7fffc29a6500 
arr_c: 0x7fffc29a64e0, pointer_c: 0x7fffc29a64e0 
*/
2.2.2 指针表示多维数组(以二维数组举例)

数组表示:

类型 变量名 [rows] [cols]

指针表示:

类型 (* 指针名)[cols]

#include <stdio.h>
#define ROW 5
#define COL 5

int main(void)
{
        int arr[ROW][COL] = {[2] = {1, 2, 3}};
        int (*p) [COL] = arr;
		
        //输出数组和指针的地址,发现它们相同  
        printf("arr_arr: %p, pointer_arr: %p \n", arr, p);

        return 0;
}
/*
输出结果:
arr_arr: 0x7fff28009820, pointer_arr: 0x7fff28009820
*/

三、函数中使用数组参数

处理数组的函数实际上用指针作为参数

3.1 定义数组形参

​ 因为函数使用指针作为参数,所以无法知道数组的长度,为了能够让函数知道数组长度,必须在形参中加一个变量 n 来代表长度。

3.1.1 两种表达方式

​ Ⅰ. 直接使用指针

​ Ⅱ. 使用数组的形式(其实就是指针)

3.1.1.1 一维数组

例:

#include <stdio.h>

/*
函数用于把数组的每个元素乘 2 再 加 5,n 表示数组长度
*/
void change_arr(int * p, int n);  // 使用指针的形式
void change2_arr(int arr[], int n);	  //使用数组的形式

int main(void)
{
        int arr[] = {1, 2, 3, 4};
        int i;

        printf("before change, arr: \n");

        for(i = 0; i < 4; i++)
                printf("%d ", arr[i]);
        printf("\n");

        //调用函数,改变arr
        change_arr(arr, 4);

        printf("after change, arr: \n");

        for(i = 0; i < 4; i++)
                printf("%d ", arr[i]);
        printf("\n");
}

//指针表示法
void change_arr(int * p, int n)
{
        int i;

        for(i = 0; i < n; i++)
                *(p + i) = 2 * (*p +i) + 1;

        return;
}

//数组表示法
void change2_arr(int arr[], int n)
{
        int i;

        for(i = 0; i < n; i++)
                arr[i] = 2 * arr[i] + 5;

        return;
}

/*
运行结果:
before change, arr: 
1 2 3 4 
after change, arr: 
7 9 11 13 
*/
3.1.1.2 二维数组
#include <stdio.h>
#define ROW 2	//定义行为 2
#define COL 4	//定义列为 4

/*
函数用于把数组的每个元素乘 2 再 加 5,n 表示数组长度
*/
void change_arr(int (* p)[COL]);  // 指针表示法
void change2_arr(int arr[][COL]);  // 数组表示法

int main(void)
{
        int arr[ROW][COL] = {{1, 2, 3, 4}, {5, 6, 7, 8}};
        int i, j;

        printf("before change, arr: \n");

        for(i = 0; i < ROW; i++)
        {
                for(j = 0; j < COL; j++)
                        printf("%d ", arr[i][j]);
                printf("\n");
        }

        //调用函数,改变arr
        change_arr(arr);
        //change2_arr(arr);

        printf("after change, arr: \n");

        for(i = 0; i < ROW; i++)
        {
                for(j = 0; j < COL; j++)
                        printf("%d ", arr[i][j]);
                printf("\n");
        }
        printf("\n");

}

//指针表示法
void change_arr(int (* p)[COL])
{
     int i, j;

        for(i = 0; i < ROW; i++)
        {
                for(j = 0; j < COL; j++)
                        *(*(p + i) + j) = 2 * (*(*(p + i) + j)) + 5;
        }

        return;
}

//数组表示法
void change2_arr(int arr[][COL]) {
        int i, j;

        for(i = 0; i < ROW; i++)
        {
                for(j = 0; j < COL; j++)
                        arr[i][j] = 2 * arr[i][j] + 5;
        }

        return;
}

/*
运行结果:
before change, arr: 
1 2 3 4 
5 6 7 8 
after change, arr: 
7 9 11 13 
15 17 19 21 
*/    

四、保护数组中的数据

编写一个处理基本类型的(如,int )的函数时,通常都是直接传递数值,只有程序需要在函数中改变该数值时,才需要传递指针;而对于数组则只能传递指针,因为这样做既节省空间又节省时间。

​ 函数中直接使用指针,很有可能会意外修改数组中的数据,为了避免这样的情况,在形式参数中使用const关键字。

4.1 const 关键字

​ Ⅰ. 创建符号常量,如 const double PI = 3.14159;

​ Ⅱ. 创建 const 数组,如 const int days [12] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

​ Ⅲ. 创建指向 const 的指针,如 const int * p = arr; //该指针指向数组的第一个元素,但不能通过指针修改该元 素

​ Ⅳ. 创建 const 指针,如 int * const p = arr; //指向数组首元素的指针,不能指向别处

4.2 const 数组

​ const 数组不能被修改

例:

#include <stdio.h>
#define SIZE 5

int main(void)
{
        const int arr[SIZE] = {};
    
        arr[2] = 4;		//改变const数组

        return 0;
}
/*
编译不通过:
const1.c: In function ‘main’:
const1.c:8:2: error: assignment of read-only location ‘arr[2]’
  arr[2] = 4;
*/

4.3 指向 const 的指针

​ 指向 const 的指针不能修改它指向的值,但可以修改本身。

例:

#include <stdio.h>
#define SIZE 5

int main(void)
{
        int arr[SIZE] = {1, 2, 3, 4, 5};
        const int * p = arr;		//把 p 指向的 int 类型声明为 const

        *p = 2;		//改变指向 const 的指针的值
    	/* 编译不通过:
    	const2.c:9:2: error: assignment of read-only location ‘*p’
        	*p = 2;
        */
    
    	//可以修改 p 本身
    	printf("p: %p \n", p);
        printf("++p: %p \n", ++p);
    	/* 运行结果:
    	p: 0x7ffdea913290 
		++p: 0x7ffdea913294
		*/
    
        return 0;
}

4.4 const 指针

​ const 指针能修改本身,但可以修改它指向的值

例:

#include <stdio.h>
#define SIZE 5

int main(void)
{
        int arr[SIZE] = {1, 2, 3, 4, 5};
        int * const p = arr;

    	//修改指针本身
        printf("p: %p \n", p);
        printf("++p: %p \n", ++p);
    	/*编译不通过:
    	const3.c: In function ‘main’:
		const3.c:10:2: error: increment of read-only variable ‘p’
  			printf("++p: %p \n", ++p);
  		*/
    	
    	//修改指针指向的值:
    	p[2] = 10;
        printf("%d \n", arr[2]);
    	/*运行结果:
    	10
    	*/
    	
        return 0;
}

4.4 const 注意事项

a. 指向 const 的指针可以用 const 数据或者非 const 数据进行初始化或赋值

​ 例:

#include <stdio.h>

int main(void)
{
        const int * p;
        const int arr_const[] = {1, 2, 3, 4, 5};
        int arr[] = {1, 2, 3, 4, 5};

        p = arr_const;  //使用const数据初始化
        printf("p: %p \n", p);

        p = arr;        //使用非const数据赋值, 这两行顺序无所谓
        printf("p: %p \n", p);

        p = &arr_const[2];
        printf("p: %p \n", p);

        p = &arr[3];
        printf("p: %p \n", p);

        return 0;
}
/*以上内容都合法
运行结果:
p: 0x7ffd256dd4e0 
p: 0x7ffd256dd4c0 
p: 0x7ffd256dd4e8 
p: 0x7ffd256dd4cc 
*/

b. 普通指针只能用非 const 数据的地址初始化或赋值。这两个规则十分合理,否则指针就能改变 const 数组中的数据

#include <stdio.h>

int main(void)
{
        int * p;	//声明普通指针
        const int arr_const[] = {1, 2, 3, 4, 5};
        int arr[] = {1, 2, 3, 4, 5};


        p = arr;        //使用非const数据赋值
        printf("p: %p \n", p);

        p = &arr[3];
        printf("p: %p \n", p);

        p = arr_const;  //使用const数据初始化
        printf("p: %p \n", p);
    	/*警告:
    	const5.c:16:4: warning: assignment discards ‘const’ qualifier from pointer target 			type [enabled by default]
 		 	p = arr_const; //使用const数据初始化
 		 */

        p = &arr_const[2];
        printf("p: %p \n", p);
    	/*警告:
    	const5.c:19:4: warning: assignment discards ‘const’ qualifier from pointer target 			type [enabled by default]
  			p = &arr_const[2];
  		*/

        return 0;
}
/*这段代码有警告
运行结果:
p: 0x7ffcb6b48520 
p: 0x7ffcb6b4852c 
p: 0x7ffcb6b48540 
p: 0x7ffcb6b48548 
*/

c. 在函数实参中也应该遵守上述两个规则, 即如果形参中没有 const 关键字,则不能传递 const 声明的数组。

例:

#include <stdio.h>
#define SIZE 5

void func(int arr[]);   //声明非 const 形 参的函数
int main(void)
{
        const int arr[] = {};

        func(arr);		//调用函数

        return 0;
}

void func(int arr[])
{
        arr[2] = 5;

        return;
}
/* 警告:
const6.c: In function ‘main’:
const6.c:9:2: warning: passing argument 1 of ‘func’ discards ‘const’ qualifier from pointer target type [enabled by default]
  func(arr);
  ^
const6.c:4:6: note: expected ‘int *’ but argument is of type ‘const int *’
 void func(int arr[]);  //声明非 const 形 参的函数
*/

参考书籍:

C Primer Plus (第六版)中文版

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值