很多人在学习C语言或者其他编程语言时,对指针的理解不是很透彻,导致在学习数据结构时,见到需要使用指针的相关算法时会有读不懂的情况,读不懂自然写不出来。本文从基础出发,以数组引入指针,逐步讲解指针以及结构体的概念与使用。如果读者能够认真地读完本文,再去阅读一些较为复杂的C语言代码会很容易。
数组是构造数据类型之一;是具有一定顺序关系的若干个变量的集合,组成数组的各个变量称为数组的元素;数组中各元素的数据类型要求相同,用数组名和下标确定。数组可以是一维的,也可以是多维的。
所谓一维数组是指只有一个下标的数组。它在计算机的内存中是连续存储的。在C语言中,一维数组的一般说明形式为:<存储类型> <数据类型> <数组名>[<表达式>]
数组名表示内存首地址,是地址常量。因此在编程中,数组名不可以修改,一旦修改,会造成数据存储位置发生变化,程序会发生重大错误。
数组名是地址常量,但是数组中的每一项都是变量,可以被赋予不同的值。
使用函数sizeof(数组名)可以得到数组占用的总内存空间,一维数组编译时分配连续内存,内存字节数=数组维数* sizeof(元素数据类型)。
C语言中不对数组进行越界检查,使用时要注意下标(从0开始,int a[5]表示a[0]-a[4]可以使用),数组定义长度是可以用变量,如int i=15;int a[i];
一维数组初始化,在定义数组时就可以为数组元素赋值,如int a[5]={1,2,3,4,5},初始化有两个原则:若在定义时不赋初值,其元素值为随机数;对static数组元素不赋初值,系统会自动赋以0值;
二维数组的一般说明形式为:<数据类型> <数组名>[常量表达式(行)] [常量表达式(列)],在声明时列数不能省略,行数可以,元素个数=行数*列数
二维数组的初始化包括分行初始化和按元素排列顺序初始化,具体包括如下情况:
具有两个或两个以上下标的数组称为多维数组,具体的使用方法与二位相似,可参考使用。
字符数组是元素的数据类型为字符类型的数组,如char c[20],ch[3][4],字符数组在初始化时常用的方法为逐个字符赋值和字符串常量(如“abc”中包含的字符为‘a’,‘b’,‘c’,‘\0’),具体如下:
在C语言中,没有字符串变量,程序员需使用字符数组来处理字符串,字符串结束的标志是’\0’,例 “hello”共5个字符,在内存占6个字节,字符串长度为5,在内存中存放的是字符的ASCII码。
在C库中实现了很多字符串处理函数,在程序中调用时必须在头文件中包含#include
strlen()的一个傻瓜式求法为:无论数组元素有多少,见到’\0’即停止,且结果不包括’\0’。
简单说明sizeof()与strlen()的区别为:strlen()求得的结果为有效长度,而sizeof()求得的结果为实际空间大小(长度)。例:char s[10]={‘A’,‘\0’,‘B’,‘C’,‘\0’,‘D’};中使用strlen()函数结果为1,使用sizeof()函数结果为10
#include
#include
int main()
{
char destination[25];
char blank[] = " ", c[]= "C++",turbo[] = "Turbo";
strcpy(destination, turbo);
strcat(destination, blank);
strcat(destination, c);
printf("%s\n", destination);
return 0;
}
上述代码中,blank表示字符空格,此为C语言基础知识。代码执行过程依次为:Turbo\0—>Turbo \0—>Turbo C++\0,输出结果为:Turbo C++
功能 | 函数 |
---|---|
复制指定长度字符串 | strncpy(p, p1, n) |
附加指定长度字符串 | strncat(p, p1, n) |
忽略大小写比较字符串 | strcasecmp |
比较指定长度字符串 | strncmp(p, p1, n) |
在字符串中查找指定字符 | strchr(p, c) |
查找字符串 | strstr(p, p1) |
检查是否为字母字符 | isalpha() |
检查是否为大写字母字符 | isupper() |
检查是否为小写字母字符 | islower() |
检查是否为数字 | isdigit() |
C程序设计中使用指针可以使程序简洁、紧凑、高效;有效地表示复杂的数据结构;动态分配内存;得到多于一个的函数返回值
在计算机内存中,每一个字节单元(这里是指内存以字节为单位,其他情况相似),都有一个编号,称为地址。当对C语言进行编译或者进行函数调用时都会分配内存空间,CPU寻找数据都需要根据这个地址从内存中找数据,变量就是对程序中数据存储空间的抽象,简单来说就是存储数据的容器里面数据是可以根据需求随时变化的。
在C语言中内存单元的编号称为指针,专门用来存放编号的变量,称为指针变量。而对指针变量也是需要分配一个内存空间的,存放变量的内存空间的编号也是指针,即指针的指针,也就是常说的二级指针。在不影响理解的情况中,有时对地址、指针和指针变量不区分,通称指针。
在这里变量a的地址就是内存中存放数据a的内存编号,把这个编号赋值给地址变量pa。&是取地址符,&a得到的结果就是a的内存编号。需要注意的是,不同数据类型下的变量所占内存大小是不同的,如int类型占4个字节,那么在内存中就需要消耗4个编号,char类型占个字节,那么在内存中就需要消耗1个编号。但是不管是什么类型,&a得到的都是该数据的第一个编号,即首地址。
指针指向的内存区域中的数据称为指针的目标。如果它指向的区域是程序中的一个变量的内存空间, 则这个变量称为指针的目标变量。简称为指针的目标。
指针的赋值运算指的是通过赋值运算符向指针变量送一个地址值。向一个指针变量赋值时,送的值必须是地址常量或指针变量,不能是普通的整数(除了赋零以外)。指针赋值运算常见的有以下几种形式:
#include
int main(int argc, char *argv[])
{
int a = 10;
int * p;
p = &a;
printf("&p:%p %d\n", &p, sizeof(p));
printf("%p %p\n", p, &a);
printf("%d %d\n", a, *p);
return 0;
}
运行结果:
oxbfb3be18 4
oxbfb3be58 oxbfb3be58
10 10
oxbfb3be1c 4
oxbfb3be58 10
指针运算是以地址量作为运算量而进行的计算,指针运算的实质就是地址的计算,这种指针运算的种类是有限的,它只能进行赋值运算、算术运算和关系运算。
指针的算术运算见下表:
运算符 | 计算形式 | 意义 |
---|---|---|
(+) | px+n | 指针向地址大的方向移动n个数据 |
(-) | px-n | 指针向地址小的方向移动n个数据 |
(++) | px++或++px | 指针向地址大的方向移动1个数据 |
(- -) | px- -或- -px | 指针向地址小的方向移动1个数据 |
(-) | px-py | 两个指针之间相隔数据元素的个数 |
在进行指针运算时有以下情况需要注意:
指针的关系运算见下表:
运算符 | 说明 | 例子 |
---|---|---|
(>) | 大于 | px>py |
(<) | 小于 | px |
(>=) | 大于等于 | px>=py |
(<=) | 小于等于 | px<=py |
(!=) | 不等于 | px!=py |
(==) | 等于 | px==py |
两指针之间的关系运算表示它们指向的地址位置之间的关系。指向地址大的指针大于指向地址小的指针。指针与一般整数变量之间的关系运算没有意义。但可以和零进行等于或不等于的关系运算,判断指针是否为空。
代码举例:
int main()
{
int a[]={5,8,7,6,2,7,3};
int y,*p=&a[1];
y=(*--p)++;
printf(“%d ”,y);
printf(“%d”,a[0]);
}
输出结果:
5 6
在C语言中,数组的指针是指数组在内存中的起始地址,数组元素的地址是指数组元素在内存中的起始地址。一维数组的数组名就是一维数组的指针(起始地址),例如:double x[8],x就是x数组的起始地址。
设指针变量px的值等于数组指针x,即指针变量px指向数组的首元素,int x[20],px;px=x;则:x[i],(px+i),px[i],*(x+i)具有完全相同的功能,就是就是访问数组第i+1个元素
在C语言中,二维数组的元素连存储,按行优先存。可以把二维数组看作是由多个一维数组组成,比如int a[3][3],含有3个元素:a[0],a[1],a[2],元素a[0]、a[1]、a[2]都是一维数组名。二维数组名代表数组的起始地址,数组名加1,是移动一行元素。因此,二维数组名常被称为行地址。
存储行地址的指针变量,叫做行指针变量。形式为,<存储类型><数据类型> (*<指针变量名>)[表达式],例如int (*p)[3],方括号中的常量表达式n表示指针每次加1就会移动n个数据。行指针操作二维数组时,表达式n一般写成1行的元素个数,即列数。
C语言通过使用字符数组来处理字符串。通常,我们把char数据类型的指针变量称为字符指针变量。字符指针变量与字符数组有着密切关系,它也被用来处理字符串。
所谓指针数组是指有若干个具有相同存储类型和数据类型的指针变量构成的集合,指针数组的一般说明形式为:<存储类型><数据类型>* <指针数组名>[<大小>],指针数组名表示该指针数组的起始地址。
把一个指向指针变量的指针变量,称为多级指针变量。对于指向处理数据的指针变量称为一级指针变量,简称一级指针,而把指向一级指针变量的指针变量称为二级指针变量,简称二级指针。二级指针变量的说明形式如下:<存储类型><数据类型>** 指针名
多级指针的运算如下:
void指针是一种不确定数据类型的指针变量,它可以通过强制类型转换让该变量指向任何数据类型的变量。一般形式为:void * <指针变量名称> ;对于void指针,在没有强制类型转换之前,不能进行任何指针的算术运算。
代码举例(一):
#include
int main(int argc, char *argv[])
{
int m = 10;
double n = 3.14;
void * p, * q;
p =(void *)&m;//赋值时也可直接写成p=&m,只要在最后使用时记得类型转换
printf("%d %d\n", m, *(int *)p);//强制类型转换
q = (void *)&n;//赋值时也可直接写成p=&n,只要在最后使用时记得类型转换
printf("%.2lf %.2lf\n", n, *(double *)q);//强制类型转换
return 0;
}
运行结果:
10 10
3.14 3.14
代码举例(二):
#include
int main(int argc, char *argv[])
{
int a[] = {5, 9, 1, 6, 9, 10};
int i, n;
void * p;
p = a;
n = sizeof(a) / sizeof(int);
for (i = 0; i < n; i++)
printf("%d ", *((int *)p + i));
return 0;
}
运行结果:
5 9 1 6 9 10
用const修饰变量时,可以将变量常量化,变量常量化是为了使得变量的值不能修改,一般说明形式如下:const <数据类型> 变量名= [<表达式>]。例如:const int m=10; m++; 这样的代码编译器就会报错。
const修饰指针时,有3种情况,分别是:常量化指针目标表达式、常量化指针变量、常量化指针变量及其目标表达式。const放在谁前面,谁不能改。]
在实际的处理对象中,有许多信息是由多个不同类型的数据组合在一起进行描述,而且这些不同类型的数据是互相联系组成了一个有机的整体。此时,就要用到一种新的构造类型数据——结构体(structure),简称结构。结构体的使用为处理复杂的数据结构(如动态数据结构等)提供了有效的手段,而且,它们为函数间传递不同类型的数据提供了方便。
结构体是用户自定义的新数据类型,在结构体中可以包含若干个不同数据类型和不同意义的数据项(当然也可以相同),从而使这些数据项组合起来反映某一个信息。例如,可以定义一个职工worker结构体,在这个结构体中包括职工编号、姓名、性别、年龄、工资、家庭住址、联系电话。这样就可以用一个结构体数据类型的变量来存放某个职工的所有相关信息。并且,用户自定义的数据类型worker也可以与int、double等基本数据类型一样,用来作为定义其他变量的数据类型。
在大括号中的内容也称为“成员列表”或“域表”。其中,每个成员名的命名规则与变量名相同;数据类型可以是基本变量类型和数组类型,或者是一个结构体类型;用分号“;”作为结束符。整个结构的定义也用分号作为结束符。
说明:结构体类型中的成员名可以与程序中的变量名相同,二者并不代表同一对象,编译程序可以自动对它们进行区分。最后,总结一下结构体类型的特点:(1)结构体类型是用户自行构造的。(2)它由若干不同的基本数据类型的数据构成。(3)它属于C语言的一种数据类型,与整型、实型相当。因此,定义它时不分配空间,只有用它定义变量时才分配空间。
结构体类型变量的定义有如下方法:
1.先定义结构体类型再定义变量名,这是C语言中定义结构体类型变量最常见的方式
2.在定义类型的同时定义变量
3.直接定义类型的同时定义变量
结构体变量是不同数据类型的若干数据的集合体。在程序中使用结构体变量时,一般情况下不能把它作为一个整体参加数据处理,而参加各种运算和操作的是结构体变量的各个成员项数据。一般来说,用结构体变量直接取分量,其操作用“·”;用指向结构体变量的指针来取分量,其操作用“->”。结构体指针会在后文细讲。结构体变量的成员用以下一般形式表示:结构体变量名.成员名。
例如,上述给出的结构体变量worker1具有下列七个成员:
worker1.number;worker1.name;worker1.sex;worker1.ageworker1.salaryworker1.addressworker1.phone
使用结构体时有以下注意事项:
1.不能将一个结构体类型变量作为一个整体加以引用,而只能对结构体类型变量中的各个成员分别引用;
2.如果成员本身又属一个结构体类型,则要用若干个成员运算符,一级一级地找到最低的一级成员。只能对最低级的成员进行赋值或存取以及运算;
3.对成员变量可以像普通变量一样进行各种运算(根据其类型决定可以进行的运算);
如:worker2.age=worker1.age; sum=worker1.age+worker2.age; worker1.age++;
4.在数组中,数组是不能彼此赋值的,而结构体类型变量可以相互赋值。在C程序中,同一结构体类型的结构体变量之间允许相互赋值,而不同结构体类型的结构体变量之间不允许相互赋值,即使两者包含有同样的成员。
与其他数据类型变量一样,也可以给结构体的每个成员赋初值,这称为结构体的初始化。结构体的初始化有如下情况:
一个结构体变量占用内存的实际大小,也可以利用sizeof求出。它的运算表达式为:sizeof(运算量)用以求出给定的运算量占用内存空间的字节数。其中运算量可以是变量、数组或结构体变量,可以是数据类型的名称。例如:sizeof(struct worker),sizeof(worker1)
具有相同结构体类型的结构体变量也可以组成数组,称它们为结构体数组。结构体数组的每一个数组元素都是结构体类型的数据,它们都分别包括各个成员(分量)项。
定义结构体数组的方法和定义结构体变量的方法相仿,只需说明其为数组即可。可以采用三种方法:
结构体数组在定义的同时也可以进行初始化,并且与结构体变量的初始化规定相同,只能对全局的或静态存储类别的结构体数组初始化。
结构体数组在使用时,一个结构体数组的元素相当于一个结构体变量,因此前面介绍的有关结构体变量的规则也适应于结构体数组元素。以上面定义的结构体数组stu[3]为例说明对结构体数组的引用:
可以设定一个指针变量用来指向一个结构体变量。此时该指针变量的值是结构体变量的起始地址,该指针称为结构体指针。
结构体指针与前面介绍的各种指针变量在特性和方法上是相同的。与前述相同,在程序中结构体指针也是通过访问目标运算“*”访问它的对象。结构体指针在程序中的一般定义形式为:
struct 结构体名 *结构指针名;其中的结构体名必须是已经定义过的结构体类型。
对于上述定义的结构体类型struct student,可以说明使用这种结构体类型的结构指针如下:struct student *pstu;
其中pstu是指向struct student结构体类型的指针。结构体指针的说明规定了它的数据特性,并为结构体指针本身分配了一定的内存空间。但是指针的内容尚未确定,即它指向随机的对象。
当表示指针变量pstu所指向的结构体变量中的成员时,(*结构体指针名).成员名这种表示形式总是需要使用圆括号,显得很不简练。因此,对于结构体指针指向的结构体成员项,给出了另外一种简洁的表示方法为:结构体指针名->成员名
它与前一种表示方法在意义上是完全等价的。例如,结构体指针pstu指向的结构体变量中的成员name可以表示如:(*pstu).name或 pstu->name
一般来说,用结构体变量直接取分量,其操作用“·”;用指向结构体变量的指针来取分量,其操作用“->”。