汉扬编程 C语言入门 嵌入式开发-C语言高级编程-(GCC、GDB)的使用

嵌入式开发-C语言高级编程-(GCC、GDB)的使用

GNU工具

嵌入式开发-C语言高级编程-(GCC、GDB)的使用

-编译工具: 把一个源程序编译为一个可执行程序-调试工具:能对执行程序进行源码或汇编级调试-软件工程工具:用于协助多人开发或大型软件项目管理,如make CVS subvision-其他工具:用于把多个目标文件链接成可执行文件的链接器,或都用作格式转换的工具1、GCC编译器简介

全称为GNU CC,GNU项上中符合ANSI C标准的编译系统编译如C、C++、Object、C 、JAVA…GCC 是可以在多种硬件平台上编译出可执行程序的超级编译器,其执行效率与一般的可编译器相比平块效率高20%以上一个交叉平台编译器,适合在嵌入式领域的开发编译。所支持后缀

– .c C原始程序- .C/.cc/.cxx C++原始程序- .m Objective-C原始程序- .i 已经处理过的C原始程序- .ii 已经处理过的C++原始程序- .s/.S 汇编语言原始程序- .h 头文件- .o 目标文件- .a/.so 库文件编译器的主要组件

分析器:将源代码转换为汇编语言汇编器:汇编语言转换为CPU可以执行的字节码链接器:标准C库:核心基本用法

GCC最基本的用法:gcc [options] [filenames]-c 只编译,不连接成可执行的文件-o output filename 确定输出文件名称为 output filename,同时这个名称不能和源文件同名-g 产生符号调试工具(GND的gdb)所必要的符号资讯,要对源代码进行调试,必须加入这个选项-O 对程序进行优化编译、连接、采用这个选项-O2 比-O更好的优化编译,连接,编译速度慢-I dirname,将dirname所指出的目录加入程序头文件目录列表中-L dirname,将dirname所指出的目录加入到程序函数档案库文件的目录列表中常见错误

一、C语法错误-错误信息:文件source.c中第几行有语法错误(syntex error)二、头文件错误-找不到头文件head.h(Can not find include file head.h)三、档案库错误-链接程序找不到所需的函数库(ld:-lm: No such file or directory)四、未定义符号-有未定义的符号(Undenfined symbo1)编译过程

1、预处理(Pre-Processing) 源文件.c 生成.i2、编译(Compiling) 文件.i生成.s3、汇编(Assembling) 文件.s生成.o4、链接(Linking) 文件.o生成可执行程序.exe例:

#include <stdio.h>#include <math.h>#define N 10#define _DEBUG_int main(){ double m =615,n; m +=N; n = sqrt(m);#ifdef _DEBUG_ printf("debug:m=%lf n=%lf\\n",m,n); #else printf("release:m=%lf n=%lf\\n",m,n); #endif return 0; }gcc -E gccdemo.c -o gccdemo.i 输出.igcc -o gccdemo gccdemo.c -lm2、GDB调试工具首先使用gcc对test.c进行编译,注意一定要加上选项 -ggcc – test.c -o testl 查看文件b 6 设置断点del 1 删除断点info b 查看断点情况p n 查看变量值n s 单步运行c 恢复程序运行help 帮助q 退出调试set 参数 设置输入参数

嵌入式C语言基础编程—5年程序员给你讲指针,精品讲解

本文主要是对C基础编程关于指针的初步讲解,后续会深入讲解C高级相关的概念(C大神可先略过)。 本人近期会陆续上传IT编程相关的资料和视频教程,可以关注一下互相交流:C C++ Java python linux ARM 嵌入式 物联网等。想学编程的朋友进入主页即可看到相关教程和资料。

嵌入式开发-C语言高级编程-(GCC、GDB)的使用

本文的主要内容:

嵌入式开发-C语言高级编程-(GCC、GDB)的使用

1地址指针的基本概念

嵌入式开发-C语言高级编程-(GCC、GDB)的使用

1.2变量的指针和指向变量的指针变量

嵌入式开发-C语言高级编程-(GCC、GDB)的使用

1.3定义一个指针变量

嵌入式开发-C语言高级编程-(GCC、GDB)的使用

1.4指针变量的引用

嵌入式开发-C语言高级编程-(GCC、GDB)的使用

2指针变量作为函数参数

嵌入式开发-C语言高级编程-(GCC、GDB)的使用

3数组指针和指向数组的指针变量

嵌入式开发-C语言高级编程-(GCC、GDB)的使用

3.1指向数组元素的指针

嵌入式开发-C语言高级编程-(GCC、GDB)的使用

3.2通过指针引用数组元素

嵌入式开发-C语言高级编程-(GCC、GDB)的使用

4指向多维数组的指针和指针变量

嵌入式开发-C语言高级编程-(GCC、GDB)的使用

5指针数组和指向指针的指针

嵌入式开发-C语言高级编程-(GCC、GDB)的使用

5.1指针数组的概念

嵌入式开发-C语言高级编程-(GCC、GDB)的使用

5.2指向指针的指针

嵌入式开发-C语言高级编程-(GCC、GDB)的使用

6.1有关指针的数据类型的小结

嵌入式开发-C语言高级编程-(GCC、GDB)的使用

6.2指针运算的小结

嵌入式开发-C语言高级编程-(GCC、GDB)的使用

指针是C语言中广泛使用的一种数据类型。运用指针编程是C语言最主要的风格之一。 利用指针变量可以表示各种数据结构;能很方便地使用数组和字符串;并能象汇编语言一样 处理内存地址,从而编出精练而高效的程序。指针极大地丰富了C语言的功能。学习指针是 学习C语言中最重要的一环,能否正确理解和使用指针是我们是否掌握C语言的一个标志。 同时,指针也是C语言中最为困难的一部分,在学习中除了要正确理解基本概念,还必须要 多编程,上机调试。只要做到这些,指针也是不难掌握的。

嵌入式开发-C语言高级编程-(GCC、GDB)的使用

8.1 地址指针的基本概念

嵌入式开发-C语言高级编程-(GCC、GDB)的使用

在计算机中,所有的数据都是存放在存储器中的。一般把存储器中的一个字节称为一个内存单元,不同的数据类型所占用的内存单元数不等,如整型量占 2 个单元,字符量占 1 个单元等,在前面已有详细的介绍。为了正确地访问这些内存单元,必须为每个内存单元编上号。根据一个内存单元的编号即可准确地找到该内存单元。内存单元的编号也叫做地址。 既然根据内存单元的编号或地址就可以找到所需的内存单元,所以通常也把这个地址称为指针。 内存单元的指针和内存单元的内容是两个不同的概念。可以用一个通俗的例子来说明它们之间的关系。我们到银行去存取款时, 银行工作人员将根据我们的帐号去找我们的存款单, 找到之后在存单上写入存款、取款的金额。在这里,帐号就是存单的指针, 存款数是存单的内容。对于一个内存单元来说,单元的地址即为指针,其中存放的数据才是该单元的内容。在C语言中,允许用一个变量来存放指针,这种变量称为指针变量。因此,一个指针变量的值 就是某个内存单元的地址或称为某内存单元的指针。

嵌入式开发-C语言高级编程-(GCC、GDB)的使用

图中,设有字符变量 C,其内容为"K"(ASCII 码为十进制数 75),C 占用了 011A 号单元(地址用十六进数表示)。设有指针变量 P,内容为 011A,这种情况我们称为 P 指向变量 C, 或说 P 是指向变量 C 的指针。

嵌入式开发-C语言高级编程-(GCC、GDB)的使用

严格地说,一个指针是一个地址,是一个常量。而一个指针变量却可以被赋予不同的指针值,是变量。但常把指针变量简称为指针。为了避免混淆,我们中约定:"指针"是指地址,是常量,"指针变量"是指取值为地址的变量。定义指针的目的是为了通过指针去访问内存单元。

嵌入式开发-C语言高级编程-(GCC、GDB)的使用

既然指针变量的值是一个地址,那么这个地址不仅可以是变量的地址,也可以是其它数 据结构的地址。在一个指针变量中存放一个数组或一个函数的首地址有何意义呢? 因为数组或函数都是连续存放的。通过访问指针变量取得了数组或函数的首地址,也就找到了该数组 或函数。这样一来,凡是出现数组,函数的地方都可以用一个指针变量来表示,只要该指针 变量中赋予数组或函数的首地址即可。这样做,将会使程序的概念十分清楚,程序本身也精 练,高效。在C语言中,一种数据类型或数据结构往往都占有一组连续的内存单元。 用"地址"这个概念并不能很好地描述一种数据类型或数据结构,而"指针"虽然实际上也是一个地址,但它却是一个数据结构的首地址,它是"指向"一个数据结构的,因而概念更为清楚, 表示更为明确。 这也是引入"指针"概念的一个重要原因。

嵌入式开发-C语言高级编程-(GCC、GDB)的使用

8.1.2 变量的指针和指向变量的指针变量

嵌入式开发-C语言高级编程-(GCC、GDB)的使用

变量的指针就是变量的地址。存放变量地址的变量是指针变量。即在C语言中,允许用 一个变量来存放指针,这种变量称为指针变量。因此,一个指针变量的值就是某个变量的地 址或称为某变量的指针。

为了表示指针变量和它所指向的变量之间的关系,在程序中用"*"符号表示"指向",例如,i_pointer 代表指针变量,而*i_pointer 是 i_pointer 所指向的变量。

因此,下面两个语句作用相同: i=3;

*i_pointer=3;

第二个语句的含义是将 3 赋给指针变量 i_pointer 所指向的变量。

8.1.3 定义一个指针变量

对指针变量的定义包括三个内容:

(1) 指针类型说明,即定义变量为一个指针变量;

(2) 指针变量名;

(3) 变量值(指针)所指向的变量的数据类型。其一般形式为:

类型说明符 *变量名; 其中,*表示这是一个指针变量,变量名即为定义的指针变量名,类型说明符表示本指针变量所指向的变量的数据类型。例如: int *p1;

表示 p1 是一个指针变量,它的值是某个整型变量的地址。或者说 p1 指向一个整型变量。至于 p1 究竟指向哪一个整型变量,应由向 p1 赋予的地址来决定。

再如:

int *p2; /*p2 是指向整型变量的指针变量*/ float *p3; /*p3 是指向浮点变量的指针变量*/ char *p4; /*p4 是指向字符变量的指针变量*/

应该注意的是,一个指针变量只能指向同类型的变量,如 P3 只能指向浮点变量,不能时而指向一个浮点变量,时而又指向一个字符变量。

8.1.4 指针变量的引用

指针变量同普通变量一样,使用之前不仅要定义说明,而且必须赋予具体的值。未经赋 值的指针变量不能使用,否则将造成系统混乱,甚至死机。指针变量的赋值只能赋予地址, 决不能赋予任何其它数据,否则将引起错误。在C语言中,变量的地址是由编译系统分配的, 对用户完全透明,用户不知道变量的具体地址。

两个有关的运算符:

1) &:取地址运算符。

2) *:指针运算符(或称"间接访问" 运算符)。

C语言中提供了地址运算符&来表示变量的地址。其一般形式为:

&变量名;

如&a 表示变量 a 的地址,&b 表示变量 b 的地址。变量本身必须预先说明。

设有指向整型变量的指针变量 p,如要把整型变量 a 的地址赋予 p 可以有以下两种方式:

(1) 指针变量初始化的方法

int a;

int *p=&a;

(2) 赋值语句的方法int a;

int *p; p=&a;

不允许把一个数赋予指针变量,故下面的赋值是错误的:

int *p;

p=1000;

被赋值的指针变量前不能再加"*"说明符,如写为*p=&a 也是错误的。假设:

int i=200, x; int *ip;

我们定义了两个整型变量 i,x,还定义了一个指向整型数的指针变量 ip。i,x 中可存放整数,而 ip 中只能存放整型变量的地址。我们可以把 i 的地址赋给 ip:

ip=&i;

此时指针变量ip 指向整型变量i,假设变量i 的地址为1800,这个赋值可形象理解为下图所示的联系。

以后我们便可以通过指针变量 ip 间接访问变量 i,例如: x=*ip;

运算符*访问以 ip 为地址的存贮区域,而 ip 中存放的是变量 i 的地址,因此,*ip 访问的是地址为 1800 的存贮区域(因为是整数,实际上是从 1800 开始的两个字节),它就是 i 所占用的存贮区域, 所以上面的赋值表达式等价于

x=i;

另外,指针变量和一般变量一样,存放在它们之中的值是可以改变的,也就是说可以改变它们的指向,假设

int i,j,*p1,*p2; i='a';

j='b'; p1=&i; p2=&j;

则建立如下图所示的联系:

这时赋值表达式: p2=p1

就使 p2 与 p1 指向同一对象 i,此时*p2 就等价于 i,而不是 j,图所示:

如果执行如下表达式:

*p2=*p1;

则表示把 p1 指向的内容赋给 p2 所指的区域, 此时就变成图所示

通过指针访问它所指向的一个变量是以间接访问的形式进行的,所以比直接访问一个变量要费时间,而且不直观,因为通过指针要访问哪一个变量,取决于指针的值(即指向),例如

"*p2=*p1;"实际上就是"j=i;",前者不仅速度慢而且目的不明。但由于指针是变量,我们可以 通过改变它们的指向,以间接访问不同的变量,这给程序员带来灵活性,也使程序代码编写得更为简洁和有效。

指针变量可出现在表达式中, 设int x,y,*px=&x;

指针变量 px 指向整数 x,则*px 可出现在 x 能出现的任何地方。例如: y=*px+5; /*表示把 x 的内容加 5 并赋给 y*/

y=++*px; /*px 的内容加上 1 之后赋给 y,++*px 相当于++(*px)*/ y=*px++; /*相当于 y=*px; px++*/

【例 8.1】

main()

{ int a,b;

int *pointer_1, *pointer_2; a=100;b=10;

pointer_1=&a; pointer_2=&b; printf("%d,%d\\n",a,b);

printf("%d,%d\\n",*pointer_1, *pointer_2);

}

对程序的说明:

1) 在开头处虽然定义了两个指针变量 pointer_1 和 pointer_2,但它们并未指向任何一个整型变量。只是提供两个指针变量,规定它们可以指向整型变量。程序第 5、6 行的作用就是使 pointer_1 指向 a,pointer_2 指向 b。

2) 最后一行的*pointer_1 和*pointer_2 就是变量 a 和 b。最后两个 printf 函数作用是相同的。

3) 程序中有两处出现*pointer_1 和*pointer_2,请区分它们的不同含义。

4) 程序第 5、6 行的"pointer_1=&a"和 "pointer_2=&b"不能写成"*pointer_1=&a"和

"*pointer_2=&b"。

请对下面再的关于"&"和"*"的问题进行考虑:

1) 如果已经执行了"pointer_1=&a;"语句,则&*pointer_1 是什么含义?

2) *&a 含义是什么?

3) (pointer_1)++和 pointer_1++的区别?

8.2 指针变量作为函数参数

函数的参数不仅可以是整型、实型、字符型等数据,还可以是指针类型。它的作用是将 一个变量的地址传送到另一个函数中。

【例 8.2】输入的两个整数按大小顺序输出。今用函数处理,而且用指针类型的数据作函数参数。

swap(int *p1,int *p2)

{int temp; temp=*p1;

*p1=*p2;

*p2=temp;

}

main()

{

int a,b;

int *pointer_1,*pointer_2; scanf("%d,%d",&a,&b); pointer_1=&a;pointer_2=&b; if(a<b) swap(pointer_1,pointer_2); printf("\\n%d,%d\\n",a,b);

}

对程序的说明:

swap 是用户定义的函数,它的作用是交换两个变量(a 和 b)的值。swap 函数的形参 p1、

p2 是指针变量。程序运行时,先执行 main 函数,输入 a 和 b 的值。然后将 a 和 b 的地址分别赋给指针变量 pointer_1 和 pointer_2,使 pointer_1 指向 a,pointer_2 指向 b。

接着执行 if 语句,由于 a〈b,因此执行 swap 函数。注意实参 pointer_1 和 pointer_2 是指针变量,在函数调用时,将实参变量的值传递给形参变量。采取的依然是"值传递"方式。因此虚实结合后形参 p1 的值为&a,p2 的值为&b。这时 p1 和 pointer_1 指向变量 a,p2 和pointer_2 指向变量 b。

接着执行执行 swap 函数的函数体使*p1 和*p2 的值互换,也就是使 a 和 b 的值互换。

函数调用结束后,p1 和 p2 不复存在(已释放)如图。

最后在 main 函数中输出的 a 和 b 的值是已经过交换的值。

请注意交换*p1 和*p2 的值是如何实现的。请找出下列程序段的错误: swap(int *p1,int *p2)

{int *temp;

*temp=*p1; /*此语句有问题*/

*p1=*p2;

*p2=temp;

}

请考虑下面的函数能否实现实现 a 和 b 互换。swap(int x,int y)

{int temp; temp=x; x=y; y=temp;

}

如果在 main 函数中用"swap(a,b);"调用 swap 函数,会有什么结果呢?请看下图所示。

7.2.1 指针变量的运算1) 赋值运算:指针变量的赋值运算有以下几种形式。

1 指针变量初始化赋值,前面已作介绍。

2 把一个变量的地址赋予指向相同数据类型的指针变量。 例如:

int a,*pa;

pa=&a; /*把整型变量 a 的地址赋予整型指针变量 pa*/

3 把一个指针变量的值赋予指向相同类型变量的另一个指针变量。 如:

int a,*pa=&a,*pb;

pb=pa; /*把 a 的地址赋予指针变量 pb*/

由于 pa,pb 均为指向整型变量的指针变量,因此可以相互赋值。

4 把数组的首地址赋予指向数组的指针变量。 例如:

int a[5],*pa; pa=a;

(数组名表示数组的首地址,故可赋予指向数组的指针变量 pa) 也可写为:

pa=&a[0]; /* 数 组 第 一 个 元 素 的 地 址 也 是 整 个 数 组 的 首 地 址 ,也可赋予 pa*/

当然也可采取初始化赋值的方法: int a[5],*pa=a;

5把字符串的首地址赋予指向字符类型的指针变量。 例如:

char *pc;

pc="C Language";

或用初始化赋值的方法写为: char *pc="C Language";

这里应说明的是并不是把整个字符串装入指针变量,而是把存放该字符串的字符数 组的首地址装入指针变量。在后面还将详细介绍。

6把函数的入口地址赋予指向函数的指针变量。 例如:

int (*pf)();

pf=f; /*f 为函数名*/

2) 加减算术运算

对于指向数组的指针变量,可以加上或减去一个整数 n。设 pa 是指向数组 a 的指针变量, 则 pa+n,pa-n,pa++,++pa,pa–,–pa 运算都是合法的。指针变量加或减一个整数 n 的意义是把指针指向的当前位置(指向某数组元素)向前或向后移动 n 个位置。应该注意,数组指针变量向前或向后移动一个位置和地址加 1 或减 1 在概念上是不同的。因为数组可以有不同的类型,各种类型的数组元素所占的字节长度是不同的。如指针变量加 1,即向后移动 1 个位置表示指针变量指向下一个数据元素的首地址。而不是在原地址基础上加 1。例如:

int a[5],*pa;

pa=a; /*pa 指向数组 a,也是指向 a[0]*/ pa=pa+2; /*pa 指向 a[2],即 pa 的值为&pa[2]*/

指针变量的加减运算只能对数组指针变量进行,对指向其它类型变量的指针变量作加减 运算是毫无意义的。

3) 两个指针变量之间的运算:只有指向同一数组的两个指针变量之间才能进行运算,否则 运算毫无意义。

【例 8.3】

main(){

int a=10,b=20,s,t,*pa,*pb; /*说明 pa,pb 为整型指针变量*/ pa=&a; /*给指针变量 pa 赋值,pa 指向变量 a*/

pb=&b; /*给指针变量 pb 赋值,pb 指向变量 b*/

s=*pa+*pb; /*求 a+b 之和,(*pa 就是 a,*pb 就是 b)*/

t=*pa**pb; /*本行是求 a*b 之积*/ printf("a=%d\\nb=%d\\na+b=%d\\na*b=%d\\n",a,b,a+b,a*b); printf("s=%d\\nt=%d\\n",s,t);

}

8.3 数组指针和指向数组的指针变量

一个变量有一个地址,一个数组包含若干元素,每个数组元素都在内存中占用存储单元, 它们都有相应的地址。所谓数组的指针是指数组的起始地址,数组元素的指针是数组元素的地址。

8.3.1 指向数组元素的指针

一个数组是由连续的一块内存单元组成的。数组名就是这块连续内存单元的首地址。一 个数组也是由各个数组元素(下标变量)组成的。每个数组元素按其类型不同占有几个连续的 内存单元。一个数组元素的首地址也是指它所占有的几个内存单元的首地址。

定义一个指向数组元素的指针变量的方法,与以前介绍的指针变量相同。 例如:

int a[10]; /*定义 a 为包含 10 个整型数据的数组*/ int *p; /*定义 p 为指向整型变量的指针*/

应当注意,因为数组为 int 型,所以指针变量也应为指向 int 型的指针变量。下面是对指针变量赋值:

p=&a[0];

把 a[0]元素的地址赋给指针变量 p。也就是说,p 指向 a 数组的第 0 号元素。

C 语言规定,数组名代表数组的首地址,也就是第 0 号元素的地址。因此,下面两个语句等价:

p=&a[0]; p=a;

在定义指针变量时可以赋给初值: int *p=&a[0];

它等效于:

int *p; p=&a[0];

当然定义时也可以写成: int *p=a;

从图中我们可以看出有以下关系:

p,a,&a[0]均指向同一单元,它们是数组 a 的首地址,也是 0 号元素 a[0]的首地址。应该说明的是 p 是变量,而 a,&a[0]都是常量。在编程时应予以注意。

数组指针变量说明的一般形式为: 类型说明符 *指针变量名;

其中类型说明符表示所指数组的类型。从一般形式可以看出指向数组的指针变量和指向普通 变量的指针变量的说明是相同的。

8.3.2 通过指针引用数组元素

C 语言规定:如果指针变量 p 已指向数组中的一个元素,则 p+1 指向同一数组中的下一个元素。

引入指针变量后,就可以用两种方法来访问数组元素了。 如果 p 的初值为&a[0],则:

1) p+i 和 a+i 就是 a[i]的地址,或者说它们指向 a 数组的第 i 个元素。

2) *(p+i)或*(a+i)就是 p+i 或 a+i 所指向的数组元素,即 a[i]。例如,*(p+5)或*(a+5)就是 a[5]。

3) 指向数组的指针变量也可以带下标,如 p[i]与*(p+i)等价。根据以上叙述,引用一个数组元素可以用:

1) 下标法,即用 a[i]形式访问数组元素。在前面介绍数组时都是采用这种方法。

2) 指针法,即采用*(a+i)或*(p+i)形式,用间接访问的方法来访问数组元素,其中 a 是数组名,p 是指向数组的指针变量,其处值 p=a。

【例 8.4】输出数组中的全部元素。(通过数组名计算元素的地址,找出元素的值) main(){

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

*(a+i)=i; for(i=0;i<10;i++)

printf("a[%d]=%d\\n",i,*(a+i));

}

8.4 指向多维数组的指针和指针变量

本小节以二维数组为例介绍多维数组的指针变量。

1. 多维数组的地址

设有整型二维数组 a[3][4]如下:

它的定义为:

int a[3][4]={{0,1,2,3},{4,5,6,7},{8,9,10,11}}

设数组 a 的首地址为 1000,各下标变量的首地址及其值如图所示。

前面介绍过,C语言允许把一个二维数组分解为多个一维数组来处理。因此数组 a 可分解为三个一维数组,即 a[0],a[1],a[2]。每一个一维数组又含有四个元素。

例如 a[0]数组,含有 a[0][0],a[0][1],a[0][2],a[0][3]四个元素。数组及数组元素的地址表示如下:

从二维数组的角度来看,a 是二维数组名,a 代表整个二维数组的首地址,也是二维数组

0 行的首地址,等于 1000。a+1 代表第一行的首地址,等于 1008。如图:

a[0]是第一个一维数组的数组名和首地址,因此也为 1000。*(a+0)或*a 是与 a[0]等效的, 它表示一维数组 a[0]0 号元素的首地址,也为 1000。&a[0][0]是二维数组 a 的 0 行 0 列元素首地址,同样是 1000。因此,a,a[0],*(a+0),*a,&a[0][0]是相等的。

同理,a+1 是二维数组 1 行的首地址,等于 1008。a[1]是第二个一维数组的数组名和首地址, 因此也为 1008。&a[1][0]是二维数组 a 的 1 行 0 列元素地址, 也是 1008。因此

a+1,a[1],*(a+1),&a[1][0]是等同的。

由此可得出:a+i,a[i],*(a+i),&a[i][0]是等同的。

此外,&a[i]和 a[i]也是等同的。因为在二维数组中不能把&a[i]理解为元素 a[i]的地址,不存在元素 a[i]。C语言规定,它是一种地址计算方法,表示数组 a 第 i 行首地址。由此, 我们得出:a[i],&a[i],*(a+i)和 a+i 也都是等同的。

另外,a[0]也可以看成是 a[0]+0,是一维数组 a[0]的 0 号元素的首地址,而 a[0]+1 则是 a[0]的 1 号元素首地址,由此可得出 a[i]+j 则是一维数组 a[i]的 j 号元素首地址,它等于&a[i][j]。

由 a[i]=*(a+i)得 a[i]+j=*(a+i)+j。由于*(a+i)+j 是二维数组 a 的 i 行 j 列元素的首地址,所以,该元素的值等于*(*(a+i)+j)。

【例 8.5】

main(){

int a[3][4]={0,1,2,3,4,5,6,7,8,9,10,11};

printf("%d,",a);

printf("%d,",*a);

printf("%d,",a[0]);

printf("%d,",&a[0]);

printf("%d\\n",&a[0][0]); printf("%d,",a+1);

printf("%d,",*(a+1));

printf("%d,",a[1]);

printf("%d,",&a[1]);

printf("%d\\n",&a[1][0]); printf("%d,",a+2);

printf("%d,",*(a+2));

printf("%d,",a[2]);

printf("%d,",&a[2]);

printf("%d\\n",&a[2][0]);

printf("%d,",a[1]+1);

printf("%d\\n",*(a+1)+1); printf("%d,%d\\n",*(a[1]+1),*(*(a+1)+1));

}

2. 指向多维数组的指针变量

把二维数组 a 分解为一维数组 a[0],a[1],a[2]之后,设 p 为指向二维数组的指针变量。可定义为:

int (*p)[4]

它表示 p 是一个指针变量,它指向包含 4 个元素的一维数组。若指向第一个一维数组

a[0],其值等于 a,a[0],或&a[0][0]等。而 p+i 则指向一维数组 a[i]。从前面的分析可得出

*(p+i)+j 是二维数组 i 行 j 列的元素的地址,而*(*(p+i)+j)则是 i 行 j 列元素的值。二维数组指针变量说明的一般形式为:

类型说明符 (*指针变量名)[长度]

其中"类型说明符"为所指数组的数据类型。"*"表示其后的变量是指针类型。"长度"表 示二维数组分解为多个一维数组时,一维数组的长度,也就是二维数组的列数。应注意"(* 指针变量名)"两边的括号不可少,如缺少括号则表示是指针数组(本章后面介绍),意义就完 全不同了。

【例 8.6】

main(){

int a[3][4]={0,1,2,3,4,5,6,7,8,9,10,11};

int(*p)[4]; int i,j; p=a;

for(i=0;i<3;i++)

{for(j=0;j<4;j++) printf("%2d ",*(*(p+i)+j)); printf("\\n");}

}

8.5 指针数组和指向指针的指针

8.5.1 指针数组的概念

一个数组的元素值为指针则是指针数组。 指针数组是一组有序的指针的集合。 指针数组的所有元素都必须是具有相同存储类型和指向相同数据类型的指针变量。

指针数组说明的一般形式为:

类型说明符 *数组名[数组长度]

其中类型说明符为指针值所指向的变量的类型。 例如:

int *pa[3]

表示 pa 是一个指针数组,它有三个数组元素,每个元素值都是一个指针,指向整型变量。

【例 8.7】通常可用一个指针数组来指向一个二维数组。指针数组中的每个元素被赋予二维数组每一行的首地址,因此也可理解为指向一个一维数组。

main(){

int a[3][3]={1,2,3,4,5,6,7,8,9};

int *pa[3]={a[0],a[1],a[2]}; int *p=a[0];

int i; for(i=0;i<3;i++)

printf("%d,%d,%d\\n",a[i][2-i],*a[i],*(*(a+i)+i)); for(i=0;i<3;i++)

printf("%d,%d,%d\\n",*pa[i],p[i],*(p+i));

}

本例程序中,pa 是一个指针数组,三个元素分别指向二维数组 a 的各行。然后用循环语句输出指定的数组元素。其中*a[i]表示 i 行 0 列元素值;*(*(a+i)+i)表示 i 行 i 列的元素值;*pa[i]表示 i 行 0 列元素值;由于 p 与 a[0]相同,故 p[i]表示 0 行 i 列的值;*(p+i)

表示 0 行 i 列的值。读者可仔细领会元素值的各种不同的表示方法。

应该注意指针数组和二维数组指针变量的区别。这两者虽然都可用来表示二维数组,但 是其表示方法和意义是不同的。

二维数组指针变量是单个的变量,其一般形式中"(*指针变量名)"两边的括号不可少。而 指针数组类型表示的是多个指针(一组有序指针)在一般形式中"*指针数组名"两边不能有括号。

例如:

int (*p)[3];

表示一个指向二维数组的指针变量。该二维数组的列数为 3 或分解为一维数组的长度为3。

int *p[3]

表示 p 是一个指针数组,有三个下标变量 p[0],p[1],p[2]均为指针变量。

指针数组也常用来表示一组字符串,这时指针数组的每个元素被赋予一个字符串的首地

址。指向字符串的指针数组的初始化更为简单。例如在例 10.32 中即采用指针数组来表示一组字符串。其初始化赋值为:

char *name[]={"Illagal day", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"};

完成这个初始化赋值之后, name[0] 即指向字符串"Illegal day" , name[1] 指向

"Monday"……。

8.5.2 指向指针的指针

如果一个指针变量存放的又是另一个指针变量的地址,则称这个指针变量为指向指针的 指针变量。

在前面已经介绍过,通过指针访问变量称为间接访问。由于指针变量直接指向变量,所 以称为"单级间址"。而如果通过指向指针的指针变量来访问变量则构成"二级间址"。

从下图可以看到,name 是一个指针数组,它的每一个元素是一个指针型数据,其值为地址。Name 是一个数据,它的每一个元素都有相应的地址。数组名 name 代表该指针数组的首地址。name+1 是 mane[i]的地址。name+1 就是指向指针型数据的指针(地址)。还可以设置一个指针变量 p,使它指向指针数组元素。P 就是指向指针型数据的指针变量。

怎样定义一个指向指针型数据的指针变量呢?如下: char **p;

p 前面有两个*号,相当于*(*p)。显然*p 是指针变量的定义形式,如果没有最前面的*,那就是定义了一个指向字符数据的指针变量。现在它前面又有一个*号,表示指针变量 p 是指向一个字符指针型变量的。*p 就是 p 所指向的另一个指针变量。

从下图可以看到,name 是一个指针数组,它的每一个元素是一个指针型数据,其值为地址。name 是一个数组,它的每一个元素都有相应的地址。数组名 name 代表该指针数组的首地址。name+1 是 mane[i]的地址。name+1 就是指向指针型数据的指针(地址)。还可以设置一个指针变量 p,使它指向指针数组元素。P 就是指向指针型数据的指针变量。

如果有:

p=name+2; printf("%o\\n",*p);

printf("%s\\n",*p);

则,第一个 printf 函数语句输出 name[2]的值(它是一个地址),第二个 printf 函数语句以字符串形式(%s)输出字符串"Great Wall"。

【例 8.8】使用指向指针的指针。

main()

{char *name[]={"Follow me","BASIC","Great Wall","FORTRAN","Computer desighn"}; char **p;

int i; for(i=0;i<5;i++)

{p=name+i; printf("%s\\n",*p);

}

}

说明:

p 是指向指针的指针变量。

有关指针的数据类型的小结

指针运算的小结现把全部指针运算列出如下:

1) 指针变量加(减)一个整数:

例如:p++、p–、p+i、p-i、p+=i、p-=i

一个指针变量加(减)一个整数并不是简单地将原值加(减)一个整数,而是将该指针 变量的原值(是一个地址)和它指向的变量所占用的内存单元字节数加(减)。

2) 指针变量赋值:将一个变量的地址赋给一个指针变量。p=&a; (将变量 a 的地址赋给 p)

p=array; (将数组 array 的首地址赋给 p) p=&array[i]; (将数组 array 第 i 个元素的地址赋给 p)

p=max; (max 为已定义的函数,将 max 的入口地址赋给 p) p1=p2; (p1 和 p2 都是指针变量,将 p2 的值赋给 p1)

注意:不能如下: p=1000;

3) 指针变量可以有空值,即该指针变量不指向任何变量: p=NULL;

4) 两个指针变量可以相减:如果两个指针变量指向同一个数组的元素,则两个指针变量值 之差是两个指针之间的元素个数。

两个指针变量比较:如果两个指针变量指向同一个数组的元素,则两个指针变量可以进行比 较。指向前面的元素的指针变量"小于" 指向后面的元素的指针变量。

本文来自网络,不代表汉扬编程立场,转载请注明出处:http://www.hyzlch.com/cjia/6967.html

嵌入式开发C语言实用方法及技巧介绍

怎样才能编写在手机上也能运行的Java程序?

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注

返回顶部