730 likes | 889 Views
第 4 讲 数组、指针、字符串. 基本要求:. ( 1 )熟练掌握一维和二维数组的定义和初始化,数 组元素的引用。 ( 2 )掌握一维字符数组和字符串,二维字符数组和字符串数组。 ( 3 )熟练掌握指针和地址的概念 ( 4 )熟练掌握指针变量的定义和初始化 ( 5 )熟练掌握通过指针引用指向实体 ( 6 )掌握通过指针变量引用数组元素及处理字符串. 数 组.
E N D
第4讲 数组、指针、字符串 基本要求: (1)熟练掌握一维和二维数组的定义和初始化,数 组元素的引用。 (2)掌握一维字符数组和字符串,二维字符数组和字符串数组。 (3)熟练掌握指针和地址的概念 (4)熟练掌握指针变量的定义和初始化 (5)熟练掌握通过指针引用指向实体 (6)掌握通过指针变量引用数组元素及处理字符串
数 组 C语言中,数组是一个构造数组类型。数组是一组相同类型(基本类型或结构类型)的变量的集合,数组中的每个数据称为数组的元素,元素在数组中按线性顺序排列。用数组名代表逻辑上相关的一批数据,每个元素用下标变量来区分,下标变量代表元素在数组中的位置。 C语言中的数组的分类: 按元素的数据类型可分为:整型数组、字符数组、实型数组、结构体数组、共用体数组、指针数组等。 按数组的维数可分为:一维数组、二维数组、多维数组。 数组在内存中占用一片连续的存储单元,最低地址对应于数组的第一个元素,最高地址对应于最后一个元素。
数 组 • 数组定义: • 一维数组: • 类型说明符 数组名 [常量表达式]; 二组数组: • 类型说明符 数组名 [常量表达式1][常量表达式2]; 例如: int a[10],x[3][4]; 注意点: 1、数组的下标从0开始, 数组名不能与其它变量名相同 2、常量表达式可以包含符号常量,但是不能是变量, C语 言不允 许对数组的大小作动态定义
数 组 • 初始化 • 定义同时赋初值 例如: int a[10]={1,2,3,4,5,6} int x[]={3,5,7,9,11} • 使用赋值语句逐个赋值 1. 可以只给前面部分元素赋初值,其余元素自动赋0。 2. 只能给元素逐个赋值,不能给数组整体赋值。 3. 二维数组可按行分段赋值,也可按行连续赋值,未赋值的自动取零。
数 组 • 初始化 • 定义同时赋初值 例如: int a[10]={1,2,3,4,5,6}; int x[]={3,5,7,9,11}; • 使用赋值语句逐个赋值 • 二维数组可按行分段赋值,也可按行连续赋值。 • 1. 按行分段赋值可写为 • int [3][3]={{80,75,92},{61,65,71},{59,63,70}}; • 2. 按行连续赋值可写为 • int a[5][3]={ 80,75,92,61,65,71,59,63, 70}; • 这两种赋初值的结果是完全相同的。
数 组 • 数组引用: • 数组名[下标] • 数组名[下标1][下标2] • 指针法引用: • 一维数组a, *(a+i) 等价:a[i] • 二维数组a * (*(a+i)+j) 等价:a[i][j] • 注意: • 1、只能通过下标变量逐个引用数组元素,而不能 • 一次引用整个数组的全部元素。 • 2、数组说明的方括号中给出的是某一维的长度;而数组引 • 用中的下 标是该元素在数组中的位置标识。前者只能是 • 常量, 后者可以是常量,变量或表达式。
指针的概念 1、地址:内存单元的编号,通常一个字节(1B)一个编号。 2、指针:内存单元的地址。 3、内存数据的访问方式: (1)直接访问——按变量名存取变量 (2)间接访问——将变量的地址存放在另一个变量(指针变量),通过指针变量来访问。 在C语言中, 允许用一个变量来存放指针,这种变量称为指针变量。一个指针变量的值就是某个内存单元的地址或称为某内存单元的指针。
指针的概念 …….. 地址 2000 30 变量i 2002 50 变量j 2004 60 变量k 指针变量 ip 2000 注意:严格地说,一个指针是一个地址, 是一个常量。而一个指针变量却可以被赋予不同的指针值,是变量。 但在常把指针变量简称为指针。为了避免混淆,我们中约定:“指针”是指地址, 是常量,“指针变量”是指取值为地址的变量。 定义指针的目的是为了通过指针去访问内存单元。
指针变量 • 变量的指针——变量的地址 • 例:&a 为变量a在内存单的存放地址,也称变量a的指针 • 指针变量——存放地址的变量 • 指向变量的指针变量——一个指针变量存放变量a的地址,则称 • 该指针变量为指向变量a的指针变量。 pa 例: int a=10; int *pa; pa=&a; a &a 10
指针变量 一、指针变量的定义: 其一般形式为: 类型说明符 *变量名; 其中,*表示这是一个指针变量,变量名即为定义的指针变量名,类型说明符表示本指针变量所指向的变量的数据类型。 例如: int *p1; 表示p1是一个指针变量,它的值是某个整型变量的地址。 或者说p1指向一个整型变量。至于p1究竟指向哪一个整型变量, 应由向p1赋予的地址来决定。
指针变量 再如: static int *p2; /*p2是指向静态整型变量的指针变量*/ float *p3; /*p3是指向浮点变量的指针变量*/ char *p4; /*p4是指向字符变量的指针变量*/ 应该注意的是,一个指针变量只能指向同类型的变量,如P3 只能指向浮点变量,不能时而指向一个浮点变量, 时而又指向一个字符变量。
指针变量 二、指针变量的赋值 指针变量同普通变量一样,使用之前不仅要定义说明,而且必须赋予具体的值。未经赋值的指针变量不能使用, 否则将造成系统混乱,甚至死机。 C语言中提供了地址运算符&来表示变量的地址。 其一般形式为: & 变量名; 例如:&a变示变量a的地址,&b表示变量b的地址。如要把整型变量a 的地址赋予p可以有以下两种方式: (1)指针变量初始化的方法: int a; int *p=&a; 也可以写成: int a, *p=&a; (2)赋值语句的方法:int a, *p; p=&a;
指针变量 三、指针变量的运算(引用) 1.指针运算符 (1) 取地址运算符 & 取地址运算符&是单目运算符,其结合性为自右至左,其功能是取变量的地址。例:&a 即变量a的地址 (2) 取内容运算符 * 取内容运算符*是单目运算符,其结合性为自右至左,用来表示指针变量所指的变量。在*运算符之后跟的变量必须是指针变量。 例:int a=10 , *pa=&a; *pa a 即为变量a 的值。
指针变量 注意:指针运算符*和指针变量说明中的指针说明符* 不是一回事。在指针变量说明中,“*”是类型说明符,表示其后的变量是指针类型。而表达式中出现的“*”则是一个运算符用以表示指针变量所指的变量。 main() { int a=5,*p=&a; printf ("%d",*p); } 表示指针变量p取得了整型变量a的地址。 本语句表示输出变量a 的值。 等价于: printf("%d",a);
指针变量 说明: 若有定义: int a, *p=&a (1) &*p 表示:p (2) *&a 表示:a (3) (*p)++ 相当于 a++ (4) *p++ 相当于 *(p++),即先取p所指向变量的值,然后,让p指向下一个存储单元。 (5) *++p 相当于 *(++p) 即先让p指向下一个存储单元,然后再取p所指向变量的值。 例: int a,b,*pa=&a,*pb=&b; pa=pb; *pa=*pb; 上面两个语句的含意是什么?
指针变量 pa a &a 10 pb &a 2 赋值运算 指针变量的赋值运算有以下几种形式: 指针变量初始化赋值,前面已作介绍。 ① 把一个变量的地址赋予指向相同数据类型的指针变量。 例如: int a,*pa; pa=&a; ②把一个指针变量的值赋予指向相同类型变量的另一个指针变量。 如: int a,*pa=&a,*pb; pb=pa; 把a的地址赋予指针变量pb, 由于pa,pb均为指向整型变量的指针变量, 因此可以相互赋值
数组的指针 p,a,&a[0] C语言规定:数组名就是这块连续内存单元的首地址(称为数组的指针)。 a[0] p+1,a+1,&a[1] 1 a[1] 3 a[2] 5 a[3] 7 一、指向数组元素的指针 9 例:int a[10], *p=a; p,a,&a[0]均指向同一单元,它们是数组a的首地址,也是0号元素a[0]的首地址。 p+i,a+i,&a[i] a[i] p+1,a+1,&a[1]均指向1号元素a[1]。 19 a[9] 类推可知p+i,a+i,&a[i]指向i号元素a[i]。
数组的指针 二、通过指针引用数组元素 引入指针变量后,就可以用两种方法来访问数组元素了。 第一种方法为下标法,即用a[i]形式访问数组元素。 第二种方法为指针法,即采用*(a+i)形式,用间接访问的方法来访问数组元素。 main() { int a[5],i,*pa=a; for(i=0;i<5;i++) {scanf("%d" pa); pa++; } pa=a; for(i=0;i<5;i++) { printf("a[%d]=%d\n",i,*pa); pa++; } } main() { int a[5],i; for(i=0;i<5;i++) scanf("%d" &a[i]); for(i=0;i<5;i++) printf("a[%d]=%d\n",i,a[i]); }
数组的指针 使用指针变量要注意的几个问题: 1、指针变量可实现本身值的改变 例如: int a[10],*p=a p++ 指向a[1]元素 可以使用下面语句来输入数组元素。 for(p=a;p<a+10;p++) scanf("%d",p); 因使用指针变量引用数组元素时要注意指针变量当前的指向,否将出错。 2、数组名表示数组的首地址,是个地址常量,不出现如下运算: × × × a++; a=&a[i]; a=p;
数组的指针 三、二维数组的指针变量 1、指向数组元素的指针变量 设有定义: int a[3][4],i,j; int *p; p=&a[0][0]; p为指向数组元素的指针变量,引用二维数组元素的方法: p+1则表示指向当前元素的下一个元素。 元素a[i][j]的地址表示为: p+4*i+j a[i][j]的值表示为: *(p+4*i+j) 如果一个n*m的二维数组的第i 行j列元素相对于首地址的增量为: i*m+j
数组的指针 例,用指向数组元素的指针变量访问二维数组 main() { int a[3][4]={0,1,2,3,4,5,6,7,8,9,10,11}; int i,j,*p; p=&a[0][0]; for(i=0;i<12;i++) {if (i%4==0) printf("\n"); printf("%2d ",*p++); } }
数组的指针 2、指向由m个元素组成的一维数组的指针变量(行指针变量) 因为n×m的二维数组a 可分解为一维数组a[0],a[1],.. .a[m-1],而它们分别又是具有m个元素的一维数组。 因此可定义一个指向由m个元素组成的一维数组的指针变量来处理二维数组。把这个指针变量称为:行指针变量 行指针变量说明的一般形式为: 类型说明符 (*指针变量名)[长度] 例:int (*p)[4] 它表示p是一个指针变量,它指向由4个元素组成的一维数组,或说p可指向一个有4列的二维数组。
2. 指向由m个整数组成的一维数组的指针变量 由于 * 运算符的级别低于 [ ] ,为了定义指针变量 p,就必须表示成 (*p)的形式。 *p 有 4 个整型元素,即 指针p 指向含有 4 个整型元素的一维数组, p 的值就是该一维数组的首地址,p是数组指针。 *p(数组) p p a 数组 (*p)[0] (*p)[1] (*p)[2] (*p)[3] a[0] a[1] a[2] p+1 p+2 定义方法: int (*p)[4]; 定义一个数组指针变量 p , 指向包含 4 个元素的一维数组。 若定义: int a[3][4]; p=&a[0]; /*p指向 a[0]*/ 则p+1 不是指向a[0][1],而是a[1],p的增值以一维数组的长度为单位。故a[i][j]可表示为:*(*(p+i)+j)。
p 1 3 5 7 9 11 13 15 17 19 21 23 p+2 例: 使用指针输出二维数组任一行任一列元素的值。 main( ) { int a[3][4]={1,3,5,7,9,11,13,15,17,19,21,23} ; int (*p)[4] , i , j ; p=a ; scanf("%d,%d" ,&i , &j) ; printf("a[%d][%d]=%d\n" , i , j , *(*(p+i)+j)) ; } 运行结果: 输入: 1, 2 <CR> 输出: a[1][2]=13 • 说明: • a[i][j]的地址可以表示为: *(p+i) + j 。注意*(p+i) + j不能写成p+ i+j。 • 对于行指针变量p,p=a; 则p指向二维数组 a 的第一行首地址 。 • 对于指针变量 p,p=a[0];则p 指向二维数组 a 的第一行第一列元素首地址, 而p=a ; 是不合法 的。
字符数组 用来存放字符量的数组称为字符数组。 字符数组的定义 字符数组类型说明的形式与前面介绍的数值数组相同。 例如: char c[10]; 定义有10个元素的数组 例如: char ch[5][10]; 即为5*10 的二维字符数组。 • 定义与初始化 注意: 若只给前面部分元素赋初值,其余元素自动赋‘\0’。 • 例如 char [10]={‘c’,‘ ’,‘p’,‘r’,‘o’,‘g’,‘r’,‘a’,‘m’}; • 其中c[9]未赋值,由系统自动赋予为空字符‘\0’ 值。
字符数组 字符串和字符串结束标志 在C语言中通常用一个字符数组来存放一个字符串。 当把一个字符串存入一个数组时, 也把结束符‘\0’存入数组,并以此作为该字符串是否结束的标志。 注意: 1、占内存单元不一 char c[]={'C', ' ','p','r','o','g','r','a','m'}; char d[]="C program"; 2、当用scanf函数输入字符串时,字符串中不能含有空格,否则将以空格作为串的结束符。
字符串的指针 char *ps="C Language"; C语言中没有字符串类型数据,处理字符串通常使用字符数组和字符指针变量。 字符串的指针是字符串首字符的地址 如: char c,*p=&c;表示p是一个指向字符变量c的指针变量。 而: char *s="C Language";则表示s是一个指向字符串的指针变量。把字符串的首地址赋予s。 例: main() { char *ps; ps="C Language"; printf("%s",ps); }
字符串的指针 例:输出字符串中n个字符后的所有字符。 main() { char *ps="this is a book"; int n=10; ps=ps+n; printf("%s\n",ps); } 运行结果为: 例:字符串的复制 main() { char a[]="this is a book",b[16]; int n; for(n=0;a[n]!='\0';n++) *(b+n)=*(a+n); *(b+n)='\0'; printf("%s",b); } book
例:字符串的复制 main() { char a[]="this is a book", char b[16],*p1,*p2; p1=a;p2=b; while(*p1!='\0') *p2++=*p1++; *p2='\0'; p2=b; printf("%s",p2); } a b p1 p2 T T h h i i s s i i s s p1 p2 问能否将程序写成如下形式? main() { char *p1="this is a book", char *p2; while(*p1!='\0') . . . /* 以下与上面相同*/ } a a b b o o o o k k \0 \0
字符串处理函数 输入输出字符串函数 ,应包含头文件"stdio.h" 使用其它字符串函数则应包含头文件"string.h" 1.字符串输出函数 puts 格式: puts (str) ; 其中str可以字符数组名或指向字符串指针变量, 它等价于: printf("%s\n",str);
字符串处理函数 #include<stdio.h> #include<string.h> void main() { char line[]="123456789"; int i, k=strlen(line); for(i=0; i<4; i++) { line[k-i]=’\0’; puts(line+i); } }
字符串处理函数 2.字符串输入函数gets 格式: gets (str); 功能:从标准输入设备键盘上输入一个字符串。本函数得到一个函数值,即为该字符数组的首地址。 说明:gets函数并不以空格作为字符串输入结束的标志,而只以回车作为输入结束。这与scanf(“%s”,…)函数不同的。
字符串处理函数 3.字符串连接函数strcat 格式: strcat (str1,str2) 功能:把字符数组str2中的字符串连接到字符数组str1 中字符串的后面,并删去字符串1后的串标志“\0”。本函数返回值是字符数组1的首地址。 (1)字符数组1必须足够大,以便容纳连接后的新字符串。 (2)连接时将字符数组1后面的‘\0’取消,在新串后保留一个‘\0’
字符串处理函数 例: 字符串连接 #include"string.h" void main() { char st1[30]="My name is “, st2[10]; printf("input your name:\n"); gets(st2); strcat(st1,st2); puts(st1); }
字符串处理函数 4.字符串拷贝函数strcpy 格式: strcpy (str1,str2) (1)复制时串结束标 志“\0”也一同拷贝。 (2)不能用一个赋值语句将一个字符串常量或字符数组直接给一个字符数组。不允许 str1=“hello”; 或 str1=str2; (3)可以用strncpy函数将字符串2中前面的若干字符复制到字符数组1中。 strncpy(str1,str2,4); (4)字符数组1必须写成数组名形式,字符数名2可以是一个字符串常量。相当于把一个字符串赋予一个字符数组。 strcpy(str1,”hello”);
字符串处理函数 例:字符复制 #include"string.h" main() { char st1[15]; char st2[]="C Language"; char st3[10]={'\0'}; strcpy(st1,st2); strncpy(st3,st1,5); puts(st1); puts(st2); puts(st3); getch(); }
字符串处理函数 下面程序段的运行结果是 #include <stdio.h> #include <string.h> main() { char a[7]="abcdef"; char b[4]="ABC"; strcpy(a,b); printf("%c\n",a[5]); puts(a); } f ABC
字符串处理函数 5.字符串比较函数strcmp 格式: strcmp(str1,str2) 功能:按照ASCII码顺序比较两个数组中的字符串, 并由函数返 回值返回比较结果。 比较规则:对两个字符串自左向右逐个比较,直到出现不相同的字符或遇到‘\n’。如果全部字符相同,则认为相同,若出现不相同的字符,则以第一个不相同的字符比较结果为准,返回是不相同字符ASCII码之差(str1-str2)。
字符串处理函数 比较结果:str1=str2, 返回值=0; str1>str2, 返回值>0; str1<str2, 返回值<0。 例: strcmp(“ABCDE ”, “ABEA“) 结果为 -2 表达式“strcmp("Windows98", "Windows95")”的值为A、0 B、3 C、1 D、-3 注意:对两个字符串比较,不能用以下形式: if(str1==str2) printf(“same”); 应该是:if(strcmp(str1,str2)==0) printf(“same”);
字符串处理函数 6.测字符串长度函数strlen 格式: strlen(str) 功能:测字符串的实际长度(不含字符串结束标志‘\0’). 例如: char st[]=“C language”; 则strlen(st)的返回值为10 char st[]="C la\ng\65u\0age" 则strlen(st)的返回值为8。 注意:strlen函数返回的是从第一个字符开始的碰到的第一个结束标志“\0”字符串中的转义字符(如\n,\65)都按一个字符计算。 7. 字符大小转换函数strlwr、strupr 格式: strlwr(str) /*大写转小写*/ strupr(str) /*小写转大写*/
字符串处理函数 下面程序的运行结果是 #include”stdio.h” #include”string.h” void main() {char a[10]=“AB”,b[10]=“LMNP”; int i=0; strcat(A,B); while(a[i++]!=‘\0’) b[i]=a[i]; puts(b); } (a) LB (b ) ABLMNP ( c) AB (d) LBLMNP √
字符串处理函数 写出运算结果 #include <stdio.h> #include <string.h> void main() { char c,*a="Office"; int i; for (i=0;i<strlen(a)/2;i++) { c=*a; strcpy(a,a+1); a[strlen(a)]=c; puts(a); } getch(); } fficeO ficeOf iceOff
字符串处理函数 函数f 的功能是将n 个字符串按由大到小的顺序进行排序 #include "string.h" void f(char p[][10], int n) {char t[20]; int i,j; for(i=0;i<n-1;i++) for(j=i+1;j<n;j++) if(strcmp(p[i],p[j]) <0) {strcpy(t,p[i]) ; strcpy(p[i],p[j]); strcpy(p[j],t);} } void main() { char p[][10]={"abc","aabdfg","abbd","dcdbe","cd"}; int i; f(p,5); for(i=0;i<n-1;i++) puts(p[i]); }
常用算法二 排序问题 排序是数据处理中最常见问题,它是将一组数据按递增或递减的次序排列。例如对一个班的学生考试成绩排序,多个商场的日均销售额排序等。排序的算法有很多种,常用的有选择法、冒泡法、插入法等。不同算法执行的效率不同,由于排序要使用数组,需要消耗较多的内存空间。下面介绍选择法、冒泡法排序。
常用算法——数据排序 7 5 3 8 9 1 4 6 数据的排序就是将一批数据由小大到(升序)或由大到小(降序)进行排列。常用的有选择法、冒泡法。 1.选择法排序 • 算法 (升序 ) • ( 设有n个数,存放在数组A(1)…..A(n)中) • 1)第1遍:从中选出最小的数,与第 1个数交换位置; • (演示)
常用算法——数据排序 1 7 7 5 3 8 9 1 4 6 • 2)第2遍:除第1 个数外,其余n-1个数中选最小的数,与第2个数交换位置; • (演示) 3)依次类推,选择了n-1次后,这个数列已按升序排列。
常用算法——数据排序 选择法排序算法的流程图:
常用算法——数据排序 选择法排序(升序)的C程序: for(i=0;i<9;i++) { p=i; for(j=i+1;j<10;j++) if(a[p]>a[j]) p=j; if(i!=p) {s=a[i]; a[i]=a[p]; a[p]=s; } //交换两个元素 printf("%d",a[i]); } 思考:如果按降序排,程序如何修改?
常用算法——数据排序 例: 输入10个数据,排升序打印输出 main() { int i,j,p,s,a[10]; printf("\n input 10 numbers:\n"); for(i=0;i<10;i++) scanf("%d",&a[i]); for(i=0;i<9;i++) { p=i; for(j=i+1;j<10;j++) if(a[p] a[j]) p=j; if(i!=p) {s=a[i]; a[i]=a[p]; a[p]=s; } } for(i=0;i<=9;i++) printf("%d",a[i]); } >
常用算法——数据排序 • 2.冒泡法排序(升序) • 算法 :(将相邻两个数比较,大数交换到后面) • 1)第 1 趟:将每相邻两个数比较,大数交换到后面,经n-1次两两相邻比较后,最大的数已交换到最后一个位置。 演示 • 2)第 2 趟:将前n-1个数(最大的数已在最后)按上法比较,经n-2次两两相邻比较后得次大的数; • 3)依次类推,n个数共进行n-1趟比较, • 在第j趟中要进行n-j次两两比较。