1.1 一维数组

一维数组的声明: int a[10]; 这里a就是一个数组. 数组a的类型就是一个指向整型的常量指针.
但是数组和指针是**不相同**的.
**数组具有特定数量的元素,而指针只是一个标量值.**
只有但数组名在表达式中使用时,编译器才会为它产生一个指针常量.(注意是指针常量,不是指针变量)

1.2 数组下标

如有 int b[10];
则 *(b + 3) 代表的就是b[3]

除了优先级之外,下标引用和间接访问完全相同

所以 array[subscript] 和 (array + (subscript) ) 完全相同.在使用下标引用的地方,你可以使用对等的指针表达式来代替.

** 下标可以是负数 **

如:

int array[10];

int ap = array + 2;

由于ap 指向array 的第三个元素, ap[-1] 指向 array的第二个元素.

下标越界是非法的

ap[9] 是非法的.

一个奇葩 2[array] **

它是合法的,把它转换成对等的间接访问表达式,为 (2 + (array) ) ,去掉内括号,交换 array 和 2 的位置, 得 (array+ 2). 也就是
array[2]

1.3 指针与下标

假定可以互换使用指针和下标.下标绝对不会比指针更有效率,但指针有时会比下标更有效率.
比较编译器对指针和下标的处理过程.:
    //下标循环
int array[10], a;
for(a = 0; a < 10; a +=1)
array[a] = 0;
为了对下标表达式求值,编译器在程序中插入指令,取得 a 的值,并把它与整型的长度(4)相乘.这个乘法需要话费一定的时间和空间.
    //指针
int array[10], *ap;
for( ap = array; ap < array + 10; ap++)
*ap = 0;
尽管这里不存在下标计算,但是还是存在乘法运算.乘法运算出现在for语句的调整部分, ap++ 中这个值必须与整型的长度相乘.然后再与指针相加.

但这里在循环每次执行的时,执行乘法运算的都是两个相同的数(1和4, 步长和整型元素长度). 结果这个乘法只是在编译时执行一次,即程序中现在包含来一条指令,把4与指针相加,程序在运行时并不执行乘法运算.

1.4 数组与指针

指针和数组是不相等的.

例子:

int a[5];

int *b;

  • 他们都具有指针指,他们都可以进行间接下标引用操作.
  • 区别:1)声明一个数组,编译器将根据声明所指定的元素数量为数组保留内存空间,然后再创建数组名,它的值是一个常量,指向这段空间的起始位置.声明一个指针变量时,编译器只为指针本身保留内存空间,它并不为任何整型值分配内存空间.2)指针变量并未初始化为指向任何现有的内存空间,如果它是一个自动变量,它甚至根本不会被初始化.

    所以 int *b;是非法的 *b 将访问内存中某个不确定的位置,或者导致程序终止. 但是b++ 可以编译通过,但是a++却不行.因为a 的值是一个常量.

1.5 作为函数参数的数组名

当一个数组名作为参数传递给一个函数时.

数组名的值就是一个指向数组第一个元素的指针.所以此时传递给函数的是一份该指针的拷贝.

函数如果执行来下标引用,实际上是对这个指针执行间接访问操作,并且通过这种间接访问,函数可以访问和修改调用程序的数组元素.

表面上看,C语言中函数的参数都是值传递,但是参数是指针的时候为什么有变成地址传递了?这是不是矛盾?

先说结论,实际上,C中所有的函数参数都是值传递.数组名也不例外.传递的参数是一份拷贝,(指向数组起始位置的指针拷贝),所以函数可以自由地操作它的指针型参,而不必担心会修改对应的作为实参的指针.

如果传递了一个指向某个变量的指针,而函数对该指针执行了间接访问操作,那么函数就可以修改那个变量.

例子:

//把第二个参数中的字符串复制到第一个参数指定的缓冲区
void strcpy(char *buffer, char const *string) {
//重复复制字符,知道遇见NULL字节
while( (*buffer++ = *string++) != '\0')
;
}

1.6 声明数组参数

如果你想把一个数组名参数传递给函数,准确的函数形参应该是怎样的?它是因该声明为一个指针还是一个数组?

实际上,调用函数时实际上传递的是一个指针,所以函数的形参实际上是一个指针.编译器实际上也接受数组形式的参数.

int strlen( char *string);

int strlen( char string);

在这里,strlen的两种传参方式是一样的.

两种声明都可以,但是哪个“更加准确呢”,答案是指针. 因为实参实际上是一个指针,而不是数组. 同样,表达式 sizeof sting的值是指向字符的指针长度,而不是数组的长度.

为什么函数原型中的一维数组形参无需写明它的元素数目,因为函数并不为数组分配内存空间.形参只是一个指针,它指向的是已经在其他地方分配好内存的空间,这个事实解释了为什么数组形参可以与任何长度的数组匹配-- 它实际传递的只是数组第一个元素的指针.另一方面,这种实现方案使函数无法知道数组的长度.如果函数需要知道数组的长度,它必须作为一个显式的参数传递给函数.

比如这样,

char *find_char(char *source, int length, char *given);

1.7 数组的初始化

int vector[5] = {10, 20 ,30, 40, 50};

静态和自动初始化

数组的初始化方式类似于标量变量的初始化方式--也就是取决于他们的存储类型.存储于静态内存的数组只初始化一次,也就是在程序开始执行之前.程序并不要执行指令把这些值放到何时的位置,他们一开始就在哪里了.这个魔术是由连接器完成的,它用包含可执行程序的文件中何时的值对数组元素进行初始化.如果数组未被初始化,数组元素的初始值将会自动设置为0. 当这个文件载入到内存中准备执行时,初始化后的数组值和程序指令一样也被载入到内存中.因此,当程序执行时,静态数组已经初始化完毕.

对于自动变量而言,初始化过程就没有那么浪漫了.因为自动变量位于运行时堆栈中,执行每次进入他们所在的代码块时,这类变量每次所处的内存位置可能并不相同.在程序开始前,编译器没有办法对这些位置进行初始化.所以自动变量在缺省情况下是未初始化的.如果自动变量的声明中给出了初始值,每次当执行流进入自动变量声明所在的作用域时,变量就被一条隐式的赋值语句初始化.这条隐式的复制语句和普通的赋值语句一样需要时间和空间来执行.数组的问题在于初始化列表中可能有很多值,这就可能产生许多条复制语句.对于那些非常盘大的数组,它的初始化时间可能非常可观.

如果数组初始化位于局部于一个函数时,你因该仔细考虑一下,在程序的执行流每次进入函数时,每次都对数组进行初始化是不是值得,如果不值得,就把数组声明为static.这样改变数组的存储区为静态区,只需在程序开始前执行一次.

1.8 自动计算数组长度

int vector[] = {1, 2, 3, 4, 5};

如果声明中并未给出数组的长度,编译器就把数组的长度设置为刚好能够容纳所有的初始值的长度. 如果初始值列表经常修改,这个技巧尤其有用.

1.9 字符数组的初始化

如果根据对整型数组的初始化,我们可以这样初始化字符数组

int a[] = {1, 2, 3, 4, 5};

char message[] = {'H', 'e', 'l', 'l', 'o', 0};

但这也太笨了.

C支持我们这样做:

char message[] = “Hello";

它看上去像是一个字符串常量,实际上并不是. 它只是前例的初始化列表的另一种写法.

如何区分字符串常量和这种初始化列表呢?

当用于初始化一个字符数组时它就是一个初始化列表,在其他任何地方,它都表示一个字符常量.

char message1[] = "Hello";//字符数组

char *message2 = "Hello";//字符常量

二者的内存分配区别图示

2、多维数组

数组的维数不只一个,就是多维数组.

int matrix[2][3];

2.1 存储顺序

int matrix[6][10];

int *mp;

mp = &matrix[3][8];

? matrix 是6行10列还是 10行6列?

答案是两种解释都可以.

因为在内存中,数组元素的实际存储方式是确定的.

2.2 数组名

一维数组名的值是一个指针常量,它的类型是“指向元素类型的指针”,它指向数组的第一个元素.

多维数组名也类似, 它指向第一维的元素实际上是另一个数组.

int matrix[3][10];

可以看作是一个一维数组,包含3个元素,只是每个元素恰好是包含10个整型元素的数组.

matrix 这个名字的值是一个指向它第一个元素的指针,matrix是一个指向一个包含10个整型元素的数组的指针.

2.3 下标

下标引用实际上只是间接表达式的一种伪装形式,在多维数组中也是如此.

matrix 指向第一个包含10个整型元素的数组.

matrix + 1 指向第二个包含10个整型元素的数组.



*(matrix + 1)

事实上标示了一个包含10个整型元素的子数组.数组名的值是一个常量指针.它指向数组的第一个元素,在这个表达式中也是如此.

它的类型是指向整型的指针.

*(matrix + 1) + 5

由于 *(matrix + 1) 是个指向整型的指针,所以5个这个值根据整型的长度进行调整.整个表达式的结果是一个指针.它指向的位置比原先那个表达式所指向的位置向后移动了5个整型元素.



对这个指针所指向的值进行间接访问操作:

*( *(matrix + 1) + 5) 它所指向的正是图中箭头标示的元素. 如果它作为右值,就取得存储于那个位置的值;如果它作为左值使用,这个位置将存储一个新值.

这个表达式实际上就是下标

*( matrix + 1) 即 matrix[1]

( (matrix + 1) + 5) 即 *( matrix[1] + 5)

还可以再次用下标代替间接访问符

即 matrix[1][5]

**注意: 不能用都好分割来表示多维数组 例如 matrix[4, 3]; 它不是 4X3的矩阵,它只是一个含有3个元素的 一维数组,因为编译器会忽略逗号前的4;

2.4 指向数组的指针

下面的声明合法嘛?

int vector[10], vp = vector;

int matrix[3][10], mp = matrix;

结论:第一个合法,第二个非法.

第一个,它为一个整型数组分配内存,并把vp声明为一个指向整型的指针,并把它初始化为指向vector数组的第一个元素.vector和vp具有相同的类型:指向整型的指针.

第二个,它正确的创建来matrix二维数组,并把mp声明为一个指向整型数组的指针.我们因该这样声明一个指向整型数组的指针,

int (
p)[10];

这个声明看起来有点复杂,但是它事实上并不是很难.你只要假定它是一个表达式并对他求值.下标优先级高于间接访问,但由于括号的存在,首先执行的还是间接访问,所以
p是个指针,但它指向什么呢?接下来是下标引用,所以p指向某种类型的数组.这个声明表达式中没有更多的操作符,所以每个数组的每个元素都是整数.

所以第二个声明加初始化后是下面这个样子:

int (p)[10] = matrix;

它使p指向matrix的第1行.

**但是不能这样声明: Int (
p)[] = matrix; 这样数组的长度不见了. **

有的编译器能捕捉到这样的错误,有的捕捉不到.

2.5 作为函数参数的多维数组

作为函数参数的多维数组名的传递方式和一维数组名相同--实际传递的是个指向数组第一个元素的指针.但是,两者之间的区别在于,多维数组的每个元素本身是另外要给数组,编译器需要知道它的维数,以便为函数形参的下标表达式进行求值.这里有两个理智,说明了他们之间的区别:

int vertor[10];

func1(vector):

参数vector的类型是指向整型的指针,所以 func1的原型可以是下面两种中的任何一种:

void func1(int *vec);

void func1(int vec[]);

作用于vec上面的指针运算把整型的长度作为它的调整因子.

对于矩阵

int matrix[3][10];

func2(matrix);

这里的参数matrix的类型是指向包含10个整型元素的数组的指针.func2的原型可以是下面两种形式中的任何一种

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

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

mat的第一个下标根据包含10个元素的整型数组的长度进行调整,接着第二个下标根据整型的长度进行调整,这个原先的matrix数组一样.

这里的关键在于,编译器必须知道第2个以及以后各维的长度才能对各下标进行求值,因此在原型中必须声明这些维的长度.第1维的长度并不需要,因为计算下标值时用不到它.

注意:

把func2 写成下面是不正确的:

void func2(int **mat);

把mat声明为一个指向整型指针的指针,它和指向整型数组的指针并不是一回事.

2.6 初始化

对于多维数组,数组元素的存储顺序就变得非常重要.

一种是只给出一个常常的初始序列的值

int matrix[2][3] = {10, 11, 12, 13, 14, 15 };

第二种,基于多维数组实际上是福大元素的一维数组这个概念.

例如 声明

int tow_dim[3][5];
int tow_dim[3][5] = {\*, \*, \*} ;//*代表一个复杂元素
int tow_dim[3][5] = {
{0, 1, 2, 3, 4,},
{10, 11, 12, 13, 14,},
{20, 21, 22, 23, 24}
};

使用缩进是非必须的,但是这样做更加有利于阅读.

3、指针数组

除了类型之外,指针变量和其他变量很相似.正如你可以创建整型数组一样,你也可以声明指针数组.

例如:

int api[10];

为了弄清楚这个复杂的声明,我们假定它是一个表达式,并对它进行求值.

下标优先级高于间接访问,所以在这个表达式中,首先执行下标引用.因此,api是某种类型的数组(它包含10个元素).在取得一个数组元素之后,随机执行的是间接访问操作.这个表达式不再有其他操作符,所以它的结果是一个整型.

对数组的某个元素执行间接访问操作后,我们得到一个整型值,所以api肯定是个数组,它的
元素类型是指向整型的指针.

关于运算符优先级:

运算符优先级.

什么地方会用到指针数组呢?

例子:

//数组
char const keyword[] = {
"do",
"for",
"if",
"register",
"return",
"switch",
"while"
};
#define N_KEYWORD\
( sizeof(keyword)/sizeof(keyword[0]) )

sizeof(keyword)是整个数组所占用的字节数.而sizeof(keyword[0])的结果则是每个元素所占的字节数.这两个值相除就是数组元素的个数.

这个数组可以用于计算一个C源文件中关键字个数的程序中.

思考

如果这样声明呢?

char const keyword[][9] = {
"do",
"for",
"if",
"register",
"return",
"switch",
"while"
};

这个声明创建了一个矩阵:它每行的长度刚好容纳最长的关键字

char const keyword[][9]的内存布局:



char const keyword[]的内存布局

矩阵的存储方式看上去效率低一些,因为它的每一行的长度都被固定为刚好能容纳最长的关键字.但是它不需要任何指针.

另外,指针数组本身也要占用空间,但是每个字符串常量占据的内存空间只能是它本身的长度.

如果字字符集中字符串的长度都差不多,那么用矩阵的方式紧凑些,

如果字符集中字符串的长度千差万别,甚至有超长字符串,可以用指针数组.

人们时常选择指针数组方案,但是略微改动

char const *keyword[] = {
"do",
"for",
"if",
"register",
"return",
"switch",
"while",
NULL
};

在表的末尾增加了一个NULL指针.这个NULL指针使函数在搜索这个表时能够检测到表的结束,而无需预先知道表的长度.

for( kwp = keyword_table; *kwp != NULL; kwp++)

C和C指针小记(十三)-数组的更多相关文章

  1. C和指针 第八章 数组

    8.1 数组名和指针 int a; int b[10]; a称为一个标量,表示一个单一的值,变量的类型是整数. b是数组,b[1]的类型是整数,b是一个指针常量,表示数组第一个元素的地址.b的类型取决 ...

  2. PHP中使用数组指针函数操作数组示例

    数组的内部指针是数组内部的组织机制,指向一个数组中的某个元素.默认是指向数组中第一个元素通过移动或改变指针的位置,可以访问数组中的任意元素.对于数组指针的控制PHP提供了以下几个内建函数可以利用. ★ ...

  3. C语言语法笔记 – 高级用法 指针数组 指针的指针 二维数组指针 结构体指针 链表 | IT宅.com

    原文:C语言语法笔记 – 高级用法 指针数组 指针的指针 二维数组指针 结构体指针 链表 | IT宅.com C语言语法笔记 – 高级用法 指针数组 指针的指针 二维数组指针 结构体指针 链表 | I ...

  4. C++笔记-数组指针/二维数组转换指针

    参考资料: 1. 作者 BensonLaur  :https://www.cnblogs.com/BensonLaur/p/6367077.html 2. https://blog.csdn.net/ ...

  5. C语言 指针基础篇 数组,函数与指针的运用 2 14

    下面看看如何在函数中运用指针吧 下面是往函数传入指针的简单操作,不是传入数组的.判断一个a是否大于b是的话给,是的话对其进行操作,不是的话就直接返回. #include <stdio.h> ...

  6. [C++]指针和指向数组的指针[一维数组与指针]

     1.一维数组与指针      形如:int型 数组 a[10]                1)&a[0]  地址常量;地址类型:int *型   ; 存储数组a的首地址          ...

  7. C和C指针小记(六)-基本声明、指针声明、typedef 、常量、作用域、链接属性、存储类型、static

    1.变量的声明 声明变量的基本形式: 说明符号(一个或者多个) 声明表达式列表 说明符 (specifier) 包含一些关键字,用于描述被声明的标识符的基本类型,它也可用户改变标识符的缺省存储类型和作 ...

  8. C和C指针小记(三)-整型,char,枚举

    1.C语言基本数据类型-整型 仅有4中机泵数据类型:整型,浮点型,指针,聚合类型(数组和结构) 整型家族:字符,短整型,整型,长整型.(都分有符号[singed]和无符号[unsinged]) 短整型 ...

  9. #运算符、不同的指针类型、数组和指针、指针运算、堆、栈、静态区、只读区、下标VS指针

    #运算符:用于在预编译期将宏参数转换为字符串 #define CONVERS(x)  #x   //注:没用双引号包括. 不同类型的指针占用的内存空间大小相同. 局部变量 定义: a[5]; 打印a[ ...

随机推荐

  1. mybatis检测mysql表是否存在

    1.优先使用information_schema来检查,如果没有查询这个的权限则使用show tables来检查. mapper: import java.util.Map; import org.a ...

  2. C#7.2——编写安全高效的C#代码 c# 中模拟一个模式匹配及匹配值抽取 走进 LINQ 的世界 移除Excel工作表密码保护小工具含C#源代码 腾讯QQ会员中心g_tk32算法【C#版】

    C#7.2——编写安全高效的C#代码 2018-11-07 18:59 by 沉睡的木木夕, 123 阅读, 0 评论, 收藏, 编辑 原文地址:https://docs.microsoft.com/ ...

  3. PHP会员找回密码功能实现实例介绍

    设置思路 1.用户注册时需要提供一个E-MAIL邮箱,目的就是用该邮箱找回密码. 2.当用户忘记密码或用户名时,点击登录页面的“找回密码”超链接,打开表单,并输入注册用的E-MAIL邮箱,提交. 3. ...

  4. MySQL和Mongodb的区别与应用场景对比

    MySQL是关系型数据库 优势: 在不同的引擎上有不同 的存储方式. 查询语句是使用传统的sql语句,拥有较为成熟的体系,成熟度很高. 开源数据库的份额在不断增加,mysql的份额页在持续增长. 缺点 ...

  5. 解决Protobuf生成的C#代码命名不规范问题

    起因 通常使用Protobuf的步骤为 定义 .proto 文件 使用 protoc 生成对应语言的代码 以生成C#代码为例,使用如下命令: protoc -I ../protos --csharp_ ...

  6. Canvas入门到高级详解(中)

    三. canvas 进阶 3.1 Canvas 颜色样式和阴影 3.1.1 设置填充和描边的颜色(掌握) fillStyle : 设置或返回用于填充绘画的颜色 strokeStyle: 设置或返回用于 ...

  7. CTF线下防御战 — 让你的靶机变成“铜墙铁壁”

    本文首发安全客,未经允许禁止转载.原文链接 一. 前言 随着CTF的普及,比赛的形式也有了越来越多的花样,对于线下赛来说,开始出现了安全加固或者防御战之类的环节,亦或者因为拿下靶机后不希望其他攻击者进 ...

  8. python实现类似于Matlab中的magic函数

    参考这篇文章的代码封装了一个类似Matlab中的magic函数,用来生成魔方矩阵. #!/usr/bin/env python # -*- coding: utf-8 -*- import numpy ...

  9. C语言 · 滑动解锁

    题目:滑动解锁 滑动解锁是智能手机一项常用的功能.你需要在3x3的点阵上,从任意一个点开始,反复移动到一个尚未经过的"相邻"的点.这些划过的点所组成的有向折线,如果与预设的折线在图 ...

  10. Direct3D 11 Tutorial 1: Basics_Direct3D 11 教程1:基础

    Github-LearnDirectX-DX3D11 tutorial01 概述 在这第一篇教程中,我们将通过介绍创建最小Direct3D应用程序所必需的元素.每一个Direct3D应用程序必需拥有这 ...